Merge remote-tracking branch 'remotes/origin/master' into add-pid-to-created-resource
This commit is contained in:
commit
8c5fe36591
|
@ -1,7 +1,9 @@
|
||||||
package example;
|
package example;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.hl7.fhir.dstu3.model.Parameters;
|
import org.hl7.fhir.dstu3.model.Parameters;
|
||||||
|
|
||||||
import ca.uhn.fhir.model.dstu2.composite.CodingDt;
|
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.StringParam;
|
||||||
import ca.uhn.fhir.rest.param.TokenAndListParam;
|
import ca.uhn.fhir.rest.param.TokenAndListParam;
|
||||||
import ca.uhn.fhir.rest.param.TokenParam;
|
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 {
|
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)
|
@Operation(name="$find-matches", idempotent=true)
|
||||||
public Parameters findMatchesBasic(
|
public Parameters findMatchesBasic(
|
||||||
@OperationParam(name="date") DateParam theDate,
|
@OperationParam(name="date") DateParam theDate,
|
||||||
|
|
|
@ -24,6 +24,8 @@ import com.google.common.collect.ArrayListMultimap;
|
||||||
import com.google.common.collect.ListMultimap;
|
import com.google.common.collect.ListMultimap;
|
||||||
import com.google.common.collect.Multimaps;
|
import com.google.common.collect.Multimaps;
|
||||||
import org.apache.commons.lang3.Validate;
|
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 javax.annotation.Nonnull;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
@ -132,4 +134,11 @@ public class HookParams {
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return new ToStringBuilder(this, ToStringStyle.SIMPLE_STYLE)
|
||||||
|
.append("params", myParams)
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ import java.io.IOException;
|
||||||
import java.io.ObjectInput;
|
import java.io.ObjectInput;
|
||||||
import java.io.ObjectOutput;
|
import java.io.ObjectOutput;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.commons.lang3.builder.EqualsBuilder;
|
import org.apache.commons.lang3.builder.EqualsBuilder;
|
||||||
import org.apache.commons.lang3.builder.HashCodeBuilder;
|
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 {
|
public void writeExternal(ObjectOutput theOut) throws IOException {
|
||||||
theOut.writeObject(getValueAsString());
|
theOut.writeObject(getValueAsString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasValue() {
|
||||||
|
return !StringUtils.isBlank(getValueAsString());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package ca.uhn.fhir.model.api;
|
package ca.uhn.fhir.model.api;
|
||||||
|
|
||||||
/*
|
/*-
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR - Core Library
|
* HAPI FHIR - Core Library
|
||||||
* %%
|
* %%
|
||||||
|
@ -20,6 +20,13 @@ package ca.uhn.fhir.model.api;
|
||||||
* #L%
|
* #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;
|
||||||
|
|
||||||
}
|
}
|
|
@ -87,4 +87,37 @@ public @interface Operation {
|
||||||
*/
|
*/
|
||||||
BundleTypeEnum bundleType() default BundleTypeEnum.COLLECTION;
|
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;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -211,6 +211,8 @@ public class Constants {
|
||||||
public static final String PARAMETER_CASCADE_DELETE = "_cascade";
|
public static final String PARAMETER_CASCADE_DELETE = "_cascade";
|
||||||
public static final String HEADER_CASCADE = "X-Cascade";
|
public static final String HEADER_CASCADE = "X-Cascade";
|
||||||
public static final String CASCADE_DELETE = "delete";
|
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 {
|
static {
|
||||||
CHARSET_UTF8 = Charset.forName(CHARSET_NAME_UTF8);
|
CHARSET_UTF8 = Charset.forName(CHARSET_NAME_UTF8);
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,253 +20,148 @@ package ca.uhn.fhir.util;
|
||||||
* #L%
|
* #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.lang.ref.SoftReference;
|
||||||
import java.text.ParsePosition;
|
import java.text.ParsePosition;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Calendar;
|
import java.util.*;
|
||||||
import java.util.Date;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.TimeZone;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A utility class for parsing and formatting HTTP dates as used in cookies and
|
* 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
|
* other headers. This class handles dates as defined by RFC 2616 section
|
||||||
* 3.3.1 as well as some other common non-standard formats.
|
* 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 {
|
public final class DateUtils {
|
||||||
|
|
||||||
/**
|
public static final TimeZone GMT = TimeZone.getTimeZone("GMT");
|
||||||
* Date format pattern used to parse HTTP date headers in RFC 1123 format.
|
/**
|
||||||
*/
|
* 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";
|
*/
|
||||||
|
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;
|
||||||
|
|
||||||
/**
|
static {
|
||||||
* Date format pattern used to parse HTTP date headers in RFC 1036 format.
|
final Calendar calendar = Calendar.getInstance();
|
||||||
*/
|
calendar.setTimeZone(GMT);
|
||||||
public static final String PATTERN_RFC1036 = "EEE, dd-MMM-yy HH:mm:ss zzz";
|
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
|
* This class should not be instantiated.
|
||||||
* {@code asctime()} format.
|
*/
|
||||||
*/
|
private DateUtils() {
|
||||||
public static final String PATTERN_ASCTIME = "EEE MMM d HH:mm:ss yyyy";
|
}
|
||||||
|
|
||||||
private static final String[] DEFAULT_PATTERNS = new String[] {
|
/**
|
||||||
PATTERN_RFC1123,
|
* A factory for {@link SimpleDateFormat}s. The instances are stored in a
|
||||||
PATTERN_RFC1036,
|
* threadlocal way because SimpleDateFormat is not thread safe as noted in
|
||||||
PATTERN_ASCTIME
|
* {@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 {
|
SimpleDateFormat format = formats.get(pattern);
|
||||||
final Calendar calendar = Calendar.getInstance();
|
if (format == null) {
|
||||||
calendar.setTimeZone(GMT);
|
format = new SimpleDateFormat(pattern, Locale.US);
|
||||||
calendar.set(2000, Calendar.JANUARY, 1, 0, 0, 0);
|
format.setTimeZone(TimeZone.getTimeZone("GMT"));
|
||||||
calendar.set(Calendar.MILLISECOND, 0);
|
formats.put(pattern, format);
|
||||||
DEFAULT_TWO_DIGIT_YEAR_START = calendar.getTime();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
return 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
}
|
||||||
* 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.
|
* 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
|
*
|
||||||
* @param dateFormats the date formats to use
|
* @param theDateValue the date value to parse
|
||||||
* @param startDate During parsing, two digit years will be placed in the range
|
* @return the parsed date or null if input could not be parsed
|
||||||
* {@code startDate} to {@code startDate + 100 years}. This value may
|
*/
|
||||||
* be {@code null}. When {@code null} is given as a parameter, year
|
public static Date parseDate(final String theDateValue) {
|
||||||
* {@code 2000} will be used.
|
notNull(theDateValue, "Date value");
|
||||||
*
|
String v = theDateValue;
|
||||||
* @return the parsed date or null if input could not be parsed
|
if (v.length() > 1 && v.startsWith("'") && v.endsWith("'")) {
|
||||||
*/
|
v = v.substring(1, v.length() - 1);
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (final String dateFormat : localDateFormats) {
|
for (final String dateFormat : DEFAULT_PATTERNS) {
|
||||||
final SimpleDateFormat dateParser = DateFormatHolder.formatFor(dateFormat);
|
final SimpleDateFormat dateParser = DateFormatHolder.formatFor(dateFormat);
|
||||||
dateParser.set2DigitYearStart(localStartDate);
|
dateParser.set2DigitYearStart(DEFAULT_TWO_DIGIT_YEAR_START);
|
||||||
final ParsePosition pos = new ParsePosition(0);
|
final ParsePosition pos = new ParsePosition(0);
|
||||||
final Date result = dateParser.parse(v, pos);
|
final Date result = dateParser.parse(v, pos);
|
||||||
if (pos.getIndex() != 0) {
|
if (pos.getIndex() != 0) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Formats the given date according to the RFC 1123 pattern.
|
* Formats the given date according to the RFC 1123 pattern.
|
||||||
*
|
*
|
||||||
* @param date The date to format.
|
* @param date The date to format.
|
||||||
* @return An RFC 1123 formatted date string.
|
* @return An RFC 1123 formatted date string.
|
||||||
*
|
* @see #PATTERN_RFC1123
|
||||||
* @see #PATTERN_RFC1123
|
*/
|
||||||
*/
|
public static String formatDate(final Date date) {
|
||||||
public static String formatDate(final Date date) {
|
notNull(date, "Date");
|
||||||
return formatDate(date, PATTERN_RFC1123);
|
notNull(PATTERN_RFC1123, "Pattern");
|
||||||
}
|
final SimpleDateFormat formatter = DateFormatHolder.formatFor(PATTERN_RFC1123);
|
||||||
|
return formatter.format(date);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
public static <T> T notNull(final T argument, final String name) {
|
||||||
* Formats the given date according to the specified pattern. The pattern
|
if (argument == null) {
|
||||||
* must conform to that used by the {@link SimpleDateFormat simple date
|
throw new IllegalArgumentException(name + " may not be null");
|
||||||
* format} class.
|
}
|
||||||
*
|
return argument;
|
||||||
* @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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import ca.uhn.fhir.rest.api.Constants;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
import com.google.common.escape.Escaper;
|
import com.google.common.escape.Escaper;
|
||||||
import com.google.common.net.PercentEscaper;
|
import com.google.common.net.PercentEscaper;
|
||||||
|
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
|
@ -44,6 +45,44 @@ public class UrlUtil {
|
||||||
private static final String URL_FORM_PARAMETER_OTHER_SAFE_CHARS = "-_.*";
|
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);
|
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.
|
* 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);
|
return PARAMETER_ESCAPER.escape(theUnescaped);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static boolean isAbsolute(String theValue) {
|
public static boolean isAbsolute(String theValue) {
|
||||||
String value = theValue.toLowerCase();
|
String value = theValue.toLowerCase();
|
||||||
return value.startsWith("http://") || value.startsWith("https://");
|
return value.startsWith("http://") || value.startsWith("https://");
|
||||||
|
@ -284,7 +322,7 @@ public class UrlUtil {
|
||||||
}
|
}
|
||||||
if (nextChar == '?') {
|
if (nextChar == '?') {
|
||||||
if (url.length() > idx + 1) {
|
if (url.length() > idx + 1) {
|
||||||
retVal.setParams(url.substring(idx + 1, url.length()));
|
retVal.setParams(url.substring(idx + 1));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -296,6 +334,18 @@ public class UrlUtil {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method specifically HTML-encodes the " and
|
||||||
|
* < 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 " and
|
* This method specifically HTML-encodes the " and
|
||||||
* < characters in order to prevent injection attacks
|
* < characters in order to prevent injection attacks
|
||||||
|
@ -352,6 +402,8 @@ public class UrlUtil {
|
||||||
char nextChar = theString.charAt(i);
|
char nextChar = theString.charAt(i);
|
||||||
if (nextChar == '%' || nextChar == '+') {
|
if (nextChar == '%' || nextChar == '+') {
|
||||||
try {
|
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");
|
return URLDecoder.decode(theString, "UTF-8");
|
||||||
} catch (UnsupportedEncodingException e) {
|
} catch (UnsupportedEncodingException e) {
|
||||||
throw new Error("UTF-8 not supported, this shouldn't happen", e);
|
throw new Error("UTF-8 not supported, this shouldn't happen", e);
|
||||||
|
@ -361,43 +413,4 @@ public class UrlUtil {
|
||||||
return theString;
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,8 @@ public interface IPrimitiveType<T> extends IBaseDatatype {
|
||||||
String getValueAsString();
|
String getValueAsString();
|
||||||
|
|
||||||
T getValue();
|
T getValue();
|
||||||
|
|
||||||
|
boolean hasValue();
|
||||||
|
|
||||||
IPrimitiveType<T> setValue(T theValue) throws IllegalArgumentException;
|
IPrimitiveType<T> setValue(T theValue) throws IllegalArgumentException;
|
||||||
|
|
||||||
|
|
|
@ -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.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.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.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}
|
ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl.cannotCreateDuplicateConceptMapUrl=Can not create multiple ConceptMap resources with ConceptMap.url "{0}", already have one with resource ID: {1}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ import ca.uhn.fhir.interceptor.api.IInterceptorService;
|
||||||
import ca.uhn.fhir.interceptor.executor.InterceptorService;
|
import ca.uhn.fhir.interceptor.executor.InterceptorService;
|
||||||
import ca.uhn.fhir.jpa.dao.DaoRegistry;
|
import ca.uhn.fhir.jpa.dao.DaoRegistry;
|
||||||
import ca.uhn.fhir.jpa.interceptor.JpaConsentContextServices;
|
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.provider.SubscriptionTriggeringProvider;
|
||||||
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
|
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
|
||||||
import ca.uhn.fhir.jpa.search.IStaleSearchDeletingSvc;
|
import ca.uhn.fhir.jpa.search.IStaleSearchDeletingSvc;
|
||||||
|
@ -120,6 +121,12 @@ public abstract class BaseConfig implements SchedulingConfigurer {
|
||||||
return new SubscriptionTriggeringProvider();
|
return new SubscriptionTriggeringProvider();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean(name = "myAttachmentBinaryAccessProvider")
|
||||||
|
@Lazy
|
||||||
|
public BinaryAccessProvider AttachmentBinaryAccessProvider() {
|
||||||
|
return new BinaryAccessProvider();
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public TaskScheduler taskScheduler() {
|
public TaskScheduler taskScheduler() {
|
||||||
ConcurrentTaskScheduler retVal = new ConcurrentTaskScheduler();
|
ConcurrentTaskScheduler retVal = new ConcurrentTaskScheduler();
|
||||||
|
|
|
@ -25,7 +25,7 @@ import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
|
||||||
import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc;
|
import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc;
|
||||||
import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
|
import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
|
||||||
import ca.uhn.fhir.jpa.util.AddRemoveCount;
|
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.jpa.util.JpaInterceptorBroadcaster;
|
||||||
import ca.uhn.fhir.model.api.IResource;
|
import ca.uhn.fhir.model.api.IResource;
|
||||||
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
|
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
|
||||||
|
|
|
@ -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(), entity.getVersion(), theExpungeOptions, theRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
return myExpungeService.expunge(getResourceName(), entity.getResourceId(), null, theExpungeOptions ,theRequest);
|
return myExpungeService.expunge(getResourceName(), entity.getResourceId(), null, theExpungeOptions, theRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -851,6 +851,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.entity;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||||
import org.apache.commons.lang3.builder.ToStringStyle;
|
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")
|
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_RES_REINDEX_JOB")
|
||||||
@Column(name = "PID")
|
@Column(name = "PID")
|
||||||
private Long myId;
|
private Long myId;
|
||||||
@Column(name = "RES_TYPE", nullable = true)
|
@Column(name = "RES_TYPE", nullable = true, length = Constants.MAX_RESOURCE_NAME_LENGTH)
|
||||||
private String myResourceType;
|
private String myResourceType;
|
||||||
/**
|
/**
|
||||||
* Inclusive
|
* Inclusive
|
||||||
|
|
|
@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.entity;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirVersionEnum;
|
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.IBaseResourceEntity;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceEncodingEnum;
|
import ca.uhn.fhir.jpa.model.entity.ResourceEncodingEnum;
|
||||||
import ca.uhn.fhir.model.primitive.IdDt;
|
import ca.uhn.fhir.model.primitive.IdDt;
|
||||||
|
@ -47,7 +48,7 @@ import java.util.Date;
|
||||||
", h.res_updated as res_updated " +
|
", h.res_updated as res_updated " +
|
||||||
", h.res_text as res_text " +
|
", h.res_text as res_text " +
|
||||||
", h.res_encoding as res_encoding " +
|
", h.res_encoding as res_encoding " +
|
||||||
", f.forced_id as forced_pid " +
|
", f.forced_id as FORCED_PID " +
|
||||||
"FROM HFJ_RES_VER h "
|
"FROM HFJ_RES_VER h "
|
||||||
+ " LEFT OUTER JOIN HFJ_FORCED_ID f ON f.resource_pid = h.res_id "
|
+ " 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")
|
+ " 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")
|
@Column(name = "RES_ID")
|
||||||
private Long myResourceId;
|
private Long myResourceId;
|
||||||
|
|
||||||
@Column(name = "RES_TYPE")
|
@Column(name = "RES_TYPE", length = Constants.MAX_RESOURCE_NAME_LENGTH)
|
||||||
private String myResourceType;
|
private String myResourceType;
|
||||||
|
|
||||||
@Column(name = "RES_VERSION")
|
@Column(name = "RES_VERSION")
|
||||||
|
@ -96,7 +97,7 @@ public class ResourceSearchView implements IBaseResourceEntity, Serializable {
|
||||||
@Enumerated(EnumType.STRING)
|
@Enumerated(EnumType.STRING)
|
||||||
private ResourceEncodingEnum myEncoding;
|
private ResourceEncodingEnum myEncoding;
|
||||||
|
|
||||||
@Column(name = "forced_pid")
|
@Column(name = "FORCED_PID", length= ForcedId.MAX_FORCED_ID_LENGTH)
|
||||||
private String myForcedPid;
|
private String myForcedPid;
|
||||||
|
|
||||||
public ResourceSearchView() {
|
public ResourceSearchView() {
|
||||||
|
|
|
@ -44,7 +44,7 @@ import static org.apache.commons.lang3.StringUtils.length;
|
||||||
@Entity
|
@Entity
|
||||||
@Indexed(interceptor = DeferConceptIndexingInterceptor.class)
|
@Indexed(interceptor = DeferConceptIndexingInterceptor.class)
|
||||||
@Table(name = "TRM_CONCEPT", uniqueConstraints = {
|
@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 = {
|
}, indexes = {
|
||||||
@Index(name = "IDX_CONCEPT_INDEXSTATUS", columnList = "INDEX_STATUS"),
|
@Index(name = "IDX_CONCEPT_INDEXSTATUS", columnList = "INDEX_STATUS"),
|
||||||
@Index(name = "IDX_CONCEPT_UPDATED", columnList = "CONCEPT_UPDATED")
|
@Index(name = "IDX_CONCEPT_UPDATED", columnList = "CONCEPT_UPDATED")
|
||||||
|
@ -60,7 +60,7 @@ public class TermConcept implements Serializable {
|
||||||
@OneToMany(fetch = FetchType.LAZY, mappedBy = "myParent", cascade = {})
|
@OneToMany(fetch = FetchType.LAZY, mappedBy = "myParent", cascade = {})
|
||||||
private Collection<TermConceptParentChildLink> myChildren;
|
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")),})
|
@Fields({@Field(name = "myCode", index = org.hibernate.search.annotations.Index.YES, store = Store.YES, analyze = Analyze.YES, analyzer = @Analyzer(definition = "exactAnalyzer")),})
|
||||||
private String myCode;
|
private String myCode;
|
||||||
@Temporal(TemporalType.TIMESTAMP)
|
@Temporal(TemporalType.TIMESTAMP)
|
||||||
|
|
|
@ -62,7 +62,7 @@ public class TermValueSet implements Serializable {
|
||||||
@Column(name = "RES_ID", insertable = false, updatable = false)
|
@Column(name = "RES_ID", insertable = false, updatable = false)
|
||||||
private Long myResourcePid;
|
private Long myResourcePid;
|
||||||
|
|
||||||
@Column(name = "NAME", nullable = true, length = MAX_NAME_LENGTH)
|
@Column(name = "VSNAME", nullable = true, length = MAX_NAME_LENGTH)
|
||||||
private String myName;
|
private String myName;
|
||||||
|
|
||||||
@OneToMany(mappedBy = "myValueSet")
|
@OneToMany(mappedBy = "myValueSet")
|
||||||
|
|
|
@ -36,7 +36,7 @@ import static org.apache.commons.lang3.StringUtils.left;
|
||||||
import static org.apache.commons.lang3.StringUtils.length;
|
import static org.apache.commons.lang3.StringUtils.length;
|
||||||
|
|
||||||
@Table(name = "TRM_VALUESET_CONCEPT", indexes = {
|
@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()
|
@Entity()
|
||||||
public class TermValueSetConcept implements Serializable {
|
public class TermValueSetConcept implements Serializable {
|
||||||
|
@ -58,10 +58,10 @@ public class TermValueSetConcept implements Serializable {
|
||||||
@Transient
|
@Transient
|
||||||
private String myValueSetName;
|
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;
|
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;
|
private String myCode;
|
||||||
|
|
||||||
@Column(name = "DISPLAY", nullable = true, length = TermConcept.MAX_DESC_LENGTH)
|
@Column(name = "DISPLAY", nullable = true, length = TermConcept.MAX_DESC_LENGTH)
|
||||||
|
|
|
@ -3,7 +3,7 @@ package ca.uhn.fhir.jpa.provider;
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.jpa.util.ExpungeOptions;
|
import ca.uhn.fhir.jpa.util.ExpungeOptions;
|
||||||
import ca.uhn.fhir.jpa.util.ExpungeOutcome;
|
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.param.DateRangeParam;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||||
|
|
|
@ -1,24 +1,12 @@
|
||||||
package ca.uhn.fhir.jpa.provider;
|
package ca.uhn.fhir.jpa.provider;
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoComposition;
|
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.dstu2.resource.Bundle;
|
|
||||||
import ca.uhn.fhir.model.dstu2.resource.Composition;
|
import ca.uhn.fhir.model.dstu2.resource.Composition;
|
||||||
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
|
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.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.hl7.fhir.instance.model.api.IBaseBundle;
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* #%L
|
* #%L
|
||||||
|
|
|
@ -21,7 +21,7 @@ package ca.uhn.fhir.jpa.provider;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoEncounter;
|
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.api.annotation.Description;
|
||||||
import ca.uhn.fhir.model.dstu2.resource.Encounter;
|
import ca.uhn.fhir.model.dstu2.resource.Encounter;
|
||||||
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
|
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
|
||||||
|
|
|
@ -25,7 +25,7 @@ import java.util.List;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoPatient;
|
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.api.annotation.Description;
|
||||||
import ca.uhn.fhir.model.dstu2.resource.Patient;
|
import ca.uhn.fhir.model.dstu2.resource.Patient;
|
||||||
import ca.uhn.fhir.model.primitive.StringDt;
|
import ca.uhn.fhir.model.primitive.StringDt;
|
||||||
|
|
|
@ -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.IFhirResourceDaoCodeSystem.LookupCodeResult;
|
||||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet;
|
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet;
|
||||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult;
|
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.CodeableConceptDt;
|
||||||
import ca.uhn.fhir.model.dstu2.composite.CodingDt;
|
import ca.uhn.fhir.model.dstu2.composite.CodingDt;
|
||||||
import ca.uhn.fhir.model.dstu2.resource.Parameters;
|
import ca.uhn.fhir.model.dstu2.resource.Parameters;
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -21,7 +21,7 @@ package ca.uhn.fhir.jpa.provider;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
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.api.IResource;
|
||||||
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
|
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
|
||||||
import ca.uhn.fhir.model.dstu2.resource.Parameters;
|
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 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> {
|
public class JpaResourceProviderDstu2<T extends IResource> extends BaseJpaResourceProvider<T> {
|
||||||
|
|
||||||
|
|
|
@ -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.IFhirSystemDao;
|
||||||
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
|
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
|
||||||
import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3;
|
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.api.annotation.Description;
|
||||||
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
|
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
|
||||||
import ca.uhn.fhir.model.dstu2.resource.Bundle;
|
import ca.uhn.fhir.model.dstu2.resource.Bundle;
|
||||||
|
|
|
@ -22,7 +22,7 @@ package ca.uhn.fhir.jpa.provider;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.jpa.subscription.ISubscriptionTriggeringSvc;
|
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.model.dstu2.valueset.ResourceTypeEnum;
|
||||||
import ca.uhn.fhir.rest.annotation.IdParam;
|
import ca.uhn.fhir.rest.annotation.IdParam;
|
||||||
import ca.uhn.fhir.rest.annotation.Operation;
|
import ca.uhn.fhir.rest.annotation.Operation;
|
||||||
|
|
|
@ -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;
|
||||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem.LookupCodeResult;
|
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.Operation;
|
||||||
import ca.uhn.fhir.rest.annotation.OperationParam;
|
import ca.uhn.fhir.rest.annotation.OperationParam;
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package ca.uhn.fhir.jpa.provider.dstu3;
|
package ca.uhn.fhir.jpa.provider.dstu3;
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoComposition;
|
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.api.annotation.Description;
|
||||||
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
|
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
|
||||||
import ca.uhn.fhir.rest.annotation.IdParam;
|
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.IBundleProvider;
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
import ca.uhn.fhir.rest.param.DateRangeParam;
|
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.dstu3.model.*;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseBundle;
|
import org.hl7.fhir.instance.model.api.IBaseBundle;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
|
||||||
import javax.print.attribute.standard.Severity;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -23,7 +23,7 @@ package ca.uhn.fhir.jpa.provider.dstu3;
|
||||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoConceptMap;
|
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoConceptMap;
|
||||||
import ca.uhn.fhir.jpa.term.TranslationRequest;
|
import ca.uhn.fhir.jpa.term.TranslationRequest;
|
||||||
import ca.uhn.fhir.jpa.term.TranslationResult;
|
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.IdParam;
|
||||||
import ca.uhn.fhir.rest.annotation.Operation;
|
import ca.uhn.fhir.rest.annotation.Operation;
|
||||||
import ca.uhn.fhir.rest.annotation.OperationParam;
|
import ca.uhn.fhir.rest.annotation.OperationParam;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package ca.uhn.fhir.jpa.provider.dstu3;
|
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.*;
|
import org.hl7.fhir.dstu3.model.*;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package ca.uhn.fhir.jpa.provider.dstu3;
|
package ca.uhn.fhir.jpa.provider.dstu3;
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoPatient;
|
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.api.annotation.Description;
|
||||||
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
|
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
|
||||||
import ca.uhn.fhir.rest.annotation.IdParam;
|
import ca.uhn.fhir.rest.annotation.IdParam;
|
||||||
|
|
|
@ -2,15 +2,13 @@ package ca.uhn.fhir.jpa.provider.dstu3;
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoStructureDefinition;
|
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoStructureDefinition;
|
||||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
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.IdParam;
|
||||||
import ca.uhn.fhir.rest.annotation.Operation;
|
import ca.uhn.fhir.rest.annotation.Operation;
|
||||||
import ca.uhn.fhir.rest.annotation.OperationParam;
|
import ca.uhn.fhir.rest.annotation.OperationParam;
|
||||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
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.param.UriParam;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
|
||||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||||
import ca.uhn.fhir.util.ValidateUtil;
|
import ca.uhn.fhir.util.ValidateUtil;
|
||||||
import org.hl7.fhir.dstu3.model.IdType;
|
import org.hl7.fhir.dstu3.model.IdType;
|
||||||
|
|
|
@ -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;
|
||||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult;
|
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.IdParam;
|
||||||
import ca.uhn.fhir.rest.annotation.Operation;
|
import ca.uhn.fhir.rest.annotation.Operation;
|
||||||
import ca.uhn.fhir.rest.annotation.OperationParam;
|
import ca.uhn.fhir.rest.annotation.OperationParam;
|
||||||
|
|
|
@ -22,7 +22,7 @@ package ca.uhn.fhir.jpa.provider.dstu3;
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
||||||
import ca.uhn.fhir.jpa.provider.BaseJpaResourceProvider;
|
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.annotation.*;
|
||||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
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 javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
import static ca.uhn.fhir.jpa.util.JpaConstants.OPERATION_META;
|
import static ca.uhn.fhir.jpa.model.util.JpaConstants.OPERATION_META;
|
||||||
import static ca.uhn.fhir.jpa.util.JpaConstants.OPERATION_META_ADD;
|
import static ca.uhn.fhir.jpa.model.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_DELETE;
|
||||||
|
|
||||||
public class JpaResourceProviderDstu3<T extends IAnyResource> extends BaseJpaResourceProvider<T> {
|
public class JpaResourceProviderDstu3<T extends IAnyResource> extends BaseJpaResourceProvider<T> {
|
||||||
|
|
||||||
|
|
|
@ -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.IFhirSystemDao;
|
||||||
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
|
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
|
||||||
import ca.uhn.fhir.jpa.provider.BaseJpaSystemProviderDstu2Plus;
|
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.model.api.annotation.Description;
|
||||||
import ca.uhn.fhir.rest.annotation.*;
|
import ca.uhn.fhir.rest.annotation.*;
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
|
|
|
@ -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;
|
||||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem.LookupCodeResult;
|
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.Operation;
|
||||||
import ca.uhn.fhir.rest.annotation.OperationParam;
|
import ca.uhn.fhir.rest.annotation.OperationParam;
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package ca.uhn.fhir.jpa.provider.r4;
|
package ca.uhn.fhir.jpa.provider.r4;
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoComposition;
|
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.api.annotation.Description;
|
||||||
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
|
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
|
||||||
import ca.uhn.fhir.rest.annotation.IdParam;
|
import ca.uhn.fhir.rest.annotation.IdParam;
|
||||||
|
|
|
@ -23,7 +23,7 @@ package ca.uhn.fhir.jpa.provider.r4;
|
||||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoConceptMap;
|
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoConceptMap;
|
||||||
import ca.uhn.fhir.jpa.term.TranslationRequest;
|
import ca.uhn.fhir.jpa.term.TranslationRequest;
|
||||||
import ca.uhn.fhir.jpa.term.TranslationResult;
|
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.IdParam;
|
||||||
import ca.uhn.fhir.rest.annotation.Operation;
|
import ca.uhn.fhir.rest.annotation.Operation;
|
||||||
import ca.uhn.fhir.rest.annotation.OperationParam;
|
import ca.uhn.fhir.rest.annotation.OperationParam;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package ca.uhn.fhir.jpa.provider.r4;
|
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.*;
|
import org.hl7.fhir.r4.model.*;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -1,17 +1,7 @@
|
||||||
package ca.uhn.fhir.jpa.provider.r4;
|
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 org.hl7.fhir.r4.model.MessageHeader;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR JPA Server
|
* HAPI FHIR JPA Server
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package ca.uhn.fhir.jpa.provider.r4;
|
package ca.uhn.fhir.jpa.provider.r4;
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoPatient;
|
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.api.annotation.Description;
|
||||||
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
|
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
|
||||||
import ca.uhn.fhir.rest.annotation.IdParam;
|
import ca.uhn.fhir.rest.annotation.IdParam;
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.provider.r4;
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoStructureDefinition;
|
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoStructureDefinition;
|
||||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
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.IdParam;
|
||||||
import ca.uhn.fhir.rest.annotation.Operation;
|
import ca.uhn.fhir.rest.annotation.Operation;
|
||||||
import ca.uhn.fhir.rest.annotation.OperationParam;
|
import ca.uhn.fhir.rest.annotation.OperationParam;
|
||||||
|
|
|
@ -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;
|
||||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult;
|
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.IdParam;
|
||||||
import ca.uhn.fhir.rest.annotation.Operation;
|
import ca.uhn.fhir.rest.annotation.Operation;
|
||||||
import ca.uhn.fhir.rest.annotation.OperationParam;
|
import ca.uhn.fhir.rest.annotation.OperationParam;
|
||||||
|
|
|
@ -22,7 +22,7 @@ package ca.uhn.fhir.jpa.provider.r4;
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
||||||
import ca.uhn.fhir.jpa.provider.BaseJpaResourceProvider;
|
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.annotation.*;
|
||||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||||
|
@ -35,9 +35,9 @@ import org.hl7.fhir.r4.model.*;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
import static ca.uhn.fhir.jpa.util.JpaConstants.OPERATION_META;
|
import static ca.uhn.fhir.jpa.model.util.JpaConstants.OPERATION_META;
|
||||||
import static ca.uhn.fhir.jpa.util.JpaConstants.OPERATION_META_ADD;
|
import static ca.uhn.fhir.jpa.model.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_DELETE;
|
||||||
|
|
||||||
public class JpaResourceProviderR4<T extends IAnyResource> extends BaseJpaResourceProvider<T> {
|
public class JpaResourceProviderR4<T extends IAnyResource> extends BaseJpaResourceProvider<T> {
|
||||||
|
|
||||||
|
|
|
@ -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.IFhirSystemDao;
|
||||||
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
|
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
|
||||||
import ca.uhn.fhir.jpa.provider.BaseJpaSystemProviderDstu2Plus;
|
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.model.api.annotation.Description;
|
||||||
import ca.uhn.fhir.rest.annotation.*;
|
import ca.uhn.fhir.rest.annotation.*;
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
|
|
|
@ -21,11 +21,14 @@ package ca.uhn.fhir.jpa.term;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.entity.TermConceptDesignation;
|
import ca.uhn.fhir.jpa.entity.TermConceptDesignation;
|
||||||
|
import ca.uhn.fhir.model.api.annotation.Block;
|
||||||
import org.hl7.fhir.r4.model.ValueSet;
|
import org.hl7.fhir.r4.model.ValueSet;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
|
@Block()
|
||||||
public class ValueSetExpansionComponentWithCodeAccumulator extends ValueSet.ValueSetExpansionComponent implements IValueSetCodeAccumulator {
|
public class ValueSetExpansionComponentWithCodeAccumulator extends ValueSet.ValueSetExpansionComponent implements IValueSetCodeAccumulator {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void includeCode(String theSystem, String theCode, String theDisplay) {
|
public void includeCode(String theSystem, String theCode, String theDisplay) {
|
||||||
ValueSet.ValueSetExpansionContainsComponent contains = this.addContains();
|
ValueSet.ValueSetExpansionContainsComponent contains = this.addContains();
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -20,29 +20,39 @@ package ca.uhn.fhir.jpa.util;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.common.reflect.ClassPath;
|
import com.google.common.reflect.ClassPath;
|
||||||
import com.google.common.reflect.ClassPath.ClassInfo;
|
import com.google.common.reflect.ClassPath.ClassInfo;
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.apache.commons.lang3.Validate;
|
import org.apache.commons.lang3.Validate;
|
||||||
|
import org.hibernate.validator.constraints.Length;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.hl7.fhir.r4.model.InstantType;
|
import org.hl7.fhir.r4.model.InstantType;
|
||||||
|
|
||||||
import javax.persistence.*;
|
import javax.persistence.*;
|
||||||
|
import javax.validation.constraints.Size;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.lang.reflect.AnnotatedElement;
|
import java.lang.reflect.AnnotatedElement;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
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.isBlank;
|
||||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
|
|
||||||
public class TestUtil {
|
public class TestUtil {
|
||||||
|
public static final int MAX_COL_LENGTH = 2000;
|
||||||
private static final int MAX_LENGTH = 30;
|
private static final int MAX_LENGTH = 30;
|
||||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TestUtil.class);
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TestUtil.class);
|
||||||
|
private static Set<String> ourReservedWords;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* non instantiable
|
* non instantiable
|
||||||
|
@ -56,6 +66,16 @@ public class TestUtil {
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("UnstableApiUsage")
|
@SuppressWarnings("UnstableApiUsage")
|
||||||
public static void scanEntities(String packageName) throws IOException, ClassNotFoundException {
|
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);
|
ImmutableSet<ClassInfo> classes = ClassPath.from(TestUtil.class.getClassLoader()).getTopLevelClasses(packageName);
|
||||||
Set<String> names = new HashSet<String>();
|
Set<String> names = new HashSet<String>();
|
||||||
|
|
||||||
|
@ -138,7 +158,10 @@ public class TestUtil {
|
||||||
|
|
||||||
JoinColumn joinColumn = theAnnotatedElement.getAnnotation(JoinColumn.class);
|
JoinColumn joinColumn = theAnnotatedElement.getAnnotation(JoinColumn.class);
|
||||||
if (joinColumn != null) {
|
if (joinColumn != null) {
|
||||||
assertNotADuplicateName(joinColumn.name(), null);
|
String columnName = joinColumn.name();
|
||||||
|
validateColumnName(columnName, theAnnotatedElement);
|
||||||
|
|
||||||
|
assertNotADuplicateName(columnName, null);
|
||||||
ForeignKey fk = joinColumn.foreignKey();
|
ForeignKey fk = joinColumn.foreignKey();
|
||||||
if (theIsSuperClass) {
|
if (theIsSuperClass) {
|
||||||
Validate.isTrue(isBlank(fk.name()), "Foreign key on " + theAnnotatedElement.toString() + " has a name() and should not as it is a superclass");
|
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);
|
Column column = theAnnotatedElement.getAnnotation(Column.class);
|
||||||
if (column != null) {
|
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());
|
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);
|
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) {
|
private static void assertEquals(String theGenerator, String theName) {
|
||||||
Validate.isTrue(theGenerator.equals(theName));
|
Validate.isTrue(theGenerator.equals(theName));
|
||||||
}
|
}
|
||||||
|
@ -209,4 +280,6 @@ public class TestUtil {
|
||||||
public static void sleepOneClick() {
|
public static void sleepOneClick() {
|
||||||
sleepAtLeast(1);
|
sleepAtLeast(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}$"));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,7 @@
|
||||||
package ca.uhn.fhir.jpa.config;
|
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.CircularQueueCaptureQueriesListener;
|
||||||
import ca.uhn.fhir.jpa.util.CurrentThreadCaptureQueriesListener;
|
import ca.uhn.fhir.jpa.util.CurrentThreadCaptureQueriesListener;
|
||||||
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
|
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
|
||||||
|
@ -156,6 +158,11 @@ public class TestR4Config extends BaseJavaConfigR4 {
|
||||||
return requestValidator;
|
return requestValidator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public IBinaryStorageSvc binaryStorage() {
|
||||||
|
return new MemoryBinaryStorageSvcImpl();
|
||||||
|
}
|
||||||
|
|
||||||
public static int getMaxThreads() {
|
public static int getMaxThreads() {
|
||||||
return ourMaxThreads;
|
return ourMaxThreads;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
|
||||||
import ca.uhn.fhir.jpa.term.VersionIndependentConcept;
|
import ca.uhn.fhir.jpa.term.VersionIndependentConcept;
|
||||||
import ca.uhn.fhir.jpa.util.CircularQueueCaptureQueriesListener;
|
import ca.uhn.fhir.jpa.util.CircularQueueCaptureQueriesListener;
|
||||||
import ca.uhn.fhir.jpa.util.ExpungeOptions;
|
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.test.utilities.LoggingRule;
|
||||||
import ca.uhn.fhir.model.dstu2.resource.Bundle;
|
import ca.uhn.fhir.model.dstu2.resource.Bundle;
|
||||||
import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry;
|
import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry;
|
||||||
|
|
|
@ -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.ModelConfig;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
|
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
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.provider.r4.JpaSystemProviderR4;
|
||||||
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
|
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
|
||||||
import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
|
import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
|
||||||
|
@ -95,6 +96,8 @@ public abstract class BaseJpaR4Test extends BaseJpaTest {
|
||||||
@Qualifier("myAllergyIntoleranceDaoR4")
|
@Qualifier("myAllergyIntoleranceDaoR4")
|
||||||
protected IFhirResourceDao<AllergyIntolerance> myAllergyIntoleranceDao;
|
protected IFhirResourceDao<AllergyIntolerance> myAllergyIntoleranceDao;
|
||||||
@Autowired
|
@Autowired
|
||||||
|
protected BinaryAccessProvider myBinaryAccessProvider;
|
||||||
|
@Autowired
|
||||||
protected ApplicationContext myAppCtx;
|
protected ApplicationContext myAppCtx;
|
||||||
@Autowired
|
@Autowired
|
||||||
@Qualifier("myAppointmentDaoR4")
|
@Qualifier("myAppointmentDaoR4")
|
||||||
|
|
|
@ -1354,7 +1354,7 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
|
||||||
for (int i = 1; i <= 9; i++) {
|
for (int i = 1; i <= 9; i++) {
|
||||||
Patient p1 = new Patient();
|
Patient p1 = new Patient();
|
||||||
p1.getBirthDateElement().setValueAsString("1980-01-0" + i);
|
p1.getBirthDateElement().setValueAsString("1980-01-0" + i);
|
||||||
String id1 = myPatientDao.create(p1).getId().toUnqualifiedVersionless().getValue();
|
myPatientDao.create(p1).getId().toUnqualifiedVersionless().getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
myDaoConfig.setSearchPreFetchThresholds(Lists.newArrayList(3, 6, 10));
|
myDaoConfig.setSearchPreFetchThresholds(Lists.newArrayList(3, 6, 10));
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package ca.uhn.fhir.jpa.dao.r4;
|
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 ca.uhn.fhir.util.TestUtil;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import org.hamcrest.Matchers;
|
import org.hamcrest.Matchers;
|
||||||
|
|
|
@ -60,7 +60,7 @@ import com.google.common.base.Charsets;
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||||
import ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl;
|
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.ExtensionDt;
|
||||||
import ca.uhn.fhir.model.api.IResource;
|
import ca.uhn.fhir.model.api.IResource;
|
||||||
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
|
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
|
||||||
|
@ -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.Medication;
|
||||||
import ca.uhn.fhir.model.dstu2.resource.MedicationAdministration;
|
import ca.uhn.fhir.model.dstu2.resource.MedicationAdministration;
|
||||||
import ca.uhn.fhir.model.dstu2.resource.MedicationOrder;
|
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.Observation;
|
||||||
import ca.uhn.fhir.model.dstu2.resource.OperationOutcome;
|
import ca.uhn.fhir.model.dstu2.resource.OperationOutcome;
|
||||||
import ca.uhn.fhir.model.dstu2.resource.Organization;
|
import ca.uhn.fhir.model.dstu2.resource.Organization;
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
package ca.uhn.fhir.jpa.provider.dstu3;
|
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.rest.server.exceptions.NotImplementedOperationException;
|
||||||
import ca.uhn.fhir.util.TestUtil;
|
import ca.uhn.fhir.util.TestUtil;
|
||||||
import org.hl7.fhir.dstu3.model.Bundle;
|
import org.hl7.fhir.dstu3.model.Bundle;
|
||||||
import org.hl7.fhir.dstu3.model.Bundle.BundleType;
|
import org.hl7.fhir.dstu3.model.Bundle.BundleType;
|
||||||
import org.hl7.fhir.dstu3.model.Composition;
|
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.dstu3.model.Parameters;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.junit.AfterClass;
|
import org.junit.AfterClass;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package ca.uhn.fhir.jpa.provider.dstu3;
|
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.InvalidRequestException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||||
import ca.uhn.fhir.util.TestUtil;
|
import ca.uhn.fhir.util.TestUtil;
|
||||||
|
|
|
@ -3,7 +3,7 @@ package ca.uhn.fhir.jpa.provider.dstu3;
|
||||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
||||||
import ca.uhn.fhir.jpa.util.ExpungeOptions;
|
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.InvalidRequestException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
|
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
|
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
package ca.uhn.fhir.jpa.provider.r4;
|
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.config.WebsocketDispatcherConfig;
|
||||||
import ca.uhn.fhir.jpa.dao.DaoRegistry;
|
import ca.uhn.fhir.jpa.dao.DaoRegistry;
|
||||||
import ca.uhn.fhir.jpa.dao.data.ISearchDao;
|
import ca.uhn.fhir.jpa.dao.data.ISearchDao;
|
||||||
import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test;
|
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.DatabaseBackedPagingProvider;
|
||||||
import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
|
import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
|
||||||
import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryR4;
|
import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryR4;
|
||||||
|
@ -101,6 +99,7 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test {
|
||||||
if (ourServer == null) {
|
if (ourServer == null) {
|
||||||
ourRestServer = new RestfulServer(myFhirCtx);
|
ourRestServer = new RestfulServer(myFhirCtx);
|
||||||
ourRestServer.registerProviders(myResourceProviders.createProviders());
|
ourRestServer.registerProviders(myResourceProviders.createProviders());
|
||||||
|
ourRestServer.registerProvider(myBinaryAccessProvider);
|
||||||
ourRestServer.getFhirContext().setNarrativeGenerator(new DefaultThymeleafNarrativeGenerator());
|
ourRestServer.getFhirContext().setNarrativeGenerator(new DefaultThymeleafNarrativeGenerator());
|
||||||
ourRestServer.setDefaultResponseEncoding(EncodingEnum.XML);
|
ourRestServer.setDefaultResponseEncoding(EncodingEnum.XML);
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.provider.r4;
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
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.MethodNotAllowedException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
|
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
package ca.uhn.fhir.jpa.provider.r4;
|
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.rest.server.exceptions.NotImplementedOperationException;
|
||||||
import ca.uhn.fhir.util.TestUtil;
|
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.instance.model.api.IIdType;
|
||||||
import org.hl7.fhir.r4.model.Bundle;
|
import org.hl7.fhir.r4.model.Bundle;
|
||||||
import org.hl7.fhir.r4.model.Bundle.BundleType;
|
import org.hl7.fhir.r4.model.Bundle.BundleType;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package ca.uhn.fhir.jpa.provider.r4;
|
package ca.uhn.fhir.jpa.provider.r4;
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoR4TerminologyTest;
|
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.InvalidRequestException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||||
import ca.uhn.fhir.util.TestUtil;
|
import ca.uhn.fhir.util.TestUtil;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package ca.uhn.fhir.jpa.provider.r4;
|
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.InvalidRequestException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||||
import ca.uhn.fhir.util.TestUtil;
|
import ca.uhn.fhir.util.TestUtil;
|
||||||
|
|
|
@ -5,8 +5,7 @@ import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||||
import ca.uhn.fhir.jpa.entity.Search;
|
import ca.uhn.fhir.jpa.entity.Search;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
|
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
|
||||||
import ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl;
|
import ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl;
|
||||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
import ca.uhn.fhir.jpa.model.util.JpaConstants;
|
||||||
import ca.uhn.fhir.jpa.util.JpaConstants;
|
|
||||||
import ca.uhn.fhir.jpa.util.TestUtil;
|
import ca.uhn.fhir.jpa.util.TestUtil;
|
||||||
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
|
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
|
||||||
import ca.uhn.fhir.model.primitive.InstantDt;
|
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.parser.StrictErrorHandler;
|
||||||
import ca.uhn.fhir.rest.api.Constants;
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
import ca.uhn.fhir.rest.api.*;
|
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.IClientInterceptor;
|
||||||
import ca.uhn.fhir.rest.client.api.IGenericClient;
|
import ca.uhn.fhir.rest.client.api.IGenericClient;
|
||||||
import ca.uhn.fhir.rest.client.api.IHttpRequest;
|
import ca.uhn.fhir.rest.client.api.IHttpRequest;
|
||||||
|
@ -72,7 +70,6 @@ import java.nio.charset.StandardCharsets;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
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 ca.uhn.fhir.jpa.util.TestUtil.sleepOneClick;
|
||||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
import static org.hamcrest.Matchers.*;
|
import static org.hamcrest.Matchers.*;
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
package ca.uhn.fhir.jpa.subscription.email;
|
package ca.uhn.fhir.jpa.subscription.email;
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
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.provider.dstu3.BaseResourceProviderDstu3Test;
|
||||||
import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil;
|
import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil;
|
||||||
import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionConstants;
|
|
||||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||||
import com.google.common.collect.Lists;
|
|
||||||
import com.icegreen.greenmail.store.FolderException;
|
import com.icegreen.greenmail.store.FolderException;
|
||||||
import com.icegreen.greenmail.util.GreenMail;
|
import com.icegreen.greenmail.util.GreenMail;
|
||||||
import com.icegreen.greenmail.util.ServerSetup;
|
import com.icegreen.greenmail.util.ServerSetup;
|
||||||
|
@ -155,10 +154,10 @@ public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test {
|
||||||
Assert.assertNotNull(subscriptionTemp);
|
Assert.assertNotNull(subscriptionTemp);
|
||||||
|
|
||||||
subscriptionTemp.getChannel().addExtension()
|
subscriptionTemp.getChannel().addExtension()
|
||||||
.setUrl(SubscriptionConstants.EXT_SUBSCRIPTION_EMAIL_FROM)
|
.setUrl(JpaConstants.EXT_SUBSCRIPTION_EMAIL_FROM)
|
||||||
.setValue(new StringType("mailto:myfrom@from.com"));
|
.setValue(new StringType("mailto:myfrom@from.com"));
|
||||||
subscriptionTemp.getChannel().addExtension()
|
subscriptionTemp.getChannel().addExtension()
|
||||||
.setUrl(SubscriptionConstants.EXT_SUBSCRIPTION_SUBJECT_TEMPLATE)
|
.setUrl(JpaConstants.EXT_SUBSCRIPTION_SUBJECT_TEMPLATE)
|
||||||
.setValue(new StringType("This is a subject"));
|
.setValue(new StringType("This is a subject"));
|
||||||
subscriptionTemp.setIdElement(subscriptionTemp.getIdElement().toUnqualifiedVersionless());
|
subscriptionTemp.setIdElement(subscriptionTemp.getIdElement().toUnqualifiedVersionless());
|
||||||
|
|
||||||
|
@ -201,10 +200,10 @@ public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test {
|
||||||
Subscription subscriptionTemp = ourClient.read(Subscription.class, sub1.getId());
|
Subscription subscriptionTemp = ourClient.read(Subscription.class, sub1.getId());
|
||||||
Assert.assertNotNull(subscriptionTemp);
|
Assert.assertNotNull(subscriptionTemp);
|
||||||
subscriptionTemp.getChannel().addExtension()
|
subscriptionTemp.getChannel().addExtension()
|
||||||
.setUrl(SubscriptionConstants.EXT_SUBSCRIPTION_EMAIL_FROM)
|
.setUrl(JpaConstants.EXT_SUBSCRIPTION_EMAIL_FROM)
|
||||||
.setValue(new StringType("myfrom@from.com"));
|
.setValue(new StringType("myfrom@from.com"));
|
||||||
subscriptionTemp.getChannel().addExtension()
|
subscriptionTemp.getChannel().addExtension()
|
||||||
.setUrl(SubscriptionConstants.EXT_SUBSCRIPTION_SUBJECT_TEMPLATE)
|
.setUrl(JpaConstants.EXT_SUBSCRIPTION_SUBJECT_TEMPLATE)
|
||||||
.setValue(new StringType("This is a subject"));
|
.setValue(new StringType("This is a subject"));
|
||||||
subscriptionTemp.setIdElement(subscriptionTemp.getIdElement().toUnqualifiedVersionless());
|
subscriptionTemp.setIdElement(subscriptionTemp.getIdElement().toUnqualifiedVersionless());
|
||||||
|
|
||||||
|
|
|
@ -3,10 +3,10 @@ package ca.uhn.fhir.jpa.subscription.resthook;
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.interceptor.api.IInterceptorService;
|
import ca.uhn.fhir.interceptor.api.IInterceptorService;
|
||||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
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.provider.dstu3.BaseResourceProviderDstu3Test;
|
||||||
import ca.uhn.fhir.jpa.subscription.NotificationServlet;
|
import ca.uhn.fhir.jpa.subscription.NotificationServlet;
|
||||||
import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil;
|
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.interceptor.SubscriptionDebugLogInterceptor;
|
||||||
import ca.uhn.fhir.jpa.subscription.module.matcher.SubscriptionMatchingStrategy;
|
import ca.uhn.fhir.jpa.subscription.module.matcher.SubscriptionMatchingStrategy;
|
||||||
import ca.uhn.fhir.rest.annotation.Create;
|
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";
|
String databaseCriteria = "Observation?code=17861-6&context.type=IHD";
|
||||||
Subscription subscription = createSubscription(databaseCriteria, null, ourNotificationListenerServer);
|
Subscription subscription = createSubscription(databaseCriteria, null, ourNotificationListenerServer);
|
||||||
List<Coding> tag = subscription.getMeta().getTag();
|
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());
|
assertEquals(SubscriptionMatchingStrategy.DATABASE.toString(), tag.get(0).getCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,7 +168,7 @@ public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test {
|
||||||
Subscription subscription = createSubscription(inMemoryCriteria, null, ourNotificationListenerServer);
|
Subscription subscription = createSubscription(inMemoryCriteria, null, ourNotificationListenerServer);
|
||||||
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(subscription));
|
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(subscription));
|
||||||
List<Coding> tag = subscription.getMeta().getTag();
|
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());
|
assertEquals(SubscriptionMatchingStrategy.IN_MEMORY.toString(), tag.get(0).getCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -468,7 +468,7 @@ public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test {
|
||||||
List<Coding> tags = subscriptionOrig.getMeta().getTag();
|
List<Coding> tags = subscriptionOrig.getMeta().getTag();
|
||||||
assertEquals(1, tags.size());
|
assertEquals(1, tags.size());
|
||||||
Coding tag = tags.get(0);
|
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(SubscriptionMatchingStrategy.IN_MEMORY.toString(), tag.getCode());
|
||||||
assertEquals("In-memory", tag.getDisplay());
|
assertEquals("In-memory", tag.getDisplay());
|
||||||
|
|
||||||
|
@ -480,7 +480,7 @@ public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test {
|
||||||
tags = subscriptionActivated.getMeta().getTag();
|
tags = subscriptionActivated.getMeta().getTag();
|
||||||
assertEquals(1, tags.size());
|
assertEquals(1, tags.size());
|
||||||
tag = tags.get(0);
|
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(SubscriptionMatchingStrategy.IN_MEMORY.toString(), tag.getCode());
|
||||||
assertEquals("In-memory", tag.getDisplay());
|
assertEquals("In-memory", tag.getDisplay());
|
||||||
}
|
}
|
||||||
|
@ -495,7 +495,7 @@ public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test {
|
||||||
List<Coding> tags = subscriptionOrig.getMeta().getTag();
|
List<Coding> tags = subscriptionOrig.getMeta().getTag();
|
||||||
assertEquals(1, tags.size());
|
assertEquals(1, tags.size());
|
||||||
Coding tag = tags.get(0);
|
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(SubscriptionMatchingStrategy.DATABASE.toString(), tag.getCode());
|
||||||
assertEquals("Database", tag.getDisplay());
|
assertEquals("Database", tag.getDisplay());
|
||||||
|
|
||||||
|
@ -507,7 +507,7 @@ public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test {
|
||||||
tags = subscription.getMeta().getTag();
|
tags = subscription.getMeta().getTag();
|
||||||
assertEquals(1, tags.size());
|
assertEquals(1, tags.size());
|
||||||
tag = tags.get(0);
|
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(SubscriptionMatchingStrategy.DATABASE.toString(), tag.getCode());
|
||||||
assertEquals("Database", tag.getDisplay());
|
assertEquals("Database", tag.getDisplay());
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
package ca.uhn.fhir.jpa.subscription.resthook;
|
package ca.uhn.fhir.jpa.subscription.resthook;
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.config.StoppableSubscriptionDeliveringRestHookSubscriber;
|
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.BaseSubscriptionsR4Test;
|
||||||
import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionConstants;
|
|
||||||
import ca.uhn.fhir.rest.api.CacheControlDirective;
|
import ca.uhn.fhir.rest.api.CacheControlDirective;
|
||||||
import ca.uhn.fhir.rest.api.Constants;
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
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 ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseBundle;
|
import org.hl7.fhir.instance.model.api.IBaseBundle;
|
||||||
import org.hl7.fhir.r4.model.*;
|
import org.hl7.fhir.r4.model.*;
|
||||||
|
@ -302,7 +300,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
|
||||||
|
|
||||||
subscription1
|
subscription1
|
||||||
.getChannel()
|
.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");
|
ourLog.info("** About to update subscription");
|
||||||
|
|
||||||
int modCount = myCountingInterceptor.getSentCount();
|
int modCount = myCountingInterceptor.getSentCount();
|
||||||
|
@ -378,7 +376,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
|
||||||
Subscription subscription = newSubscription(criteria1, payload);
|
Subscription subscription = newSubscription(criteria1, payload);
|
||||||
subscription
|
subscription
|
||||||
.getChannel()
|
.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();
|
ourClient.create().resource(subscription).execute();
|
||||||
|
|
||||||
waitForActivatedSubscriptionCount(1);
|
waitForActivatedSubscriptionCount(1);
|
||||||
|
|
|
@ -6,7 +6,7 @@ import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider;
|
||||||
import ca.uhn.fhir.jpa.provider.dstu3.BaseResourceProviderDstu3Test;
|
import ca.uhn.fhir.jpa.provider.dstu3.BaseResourceProviderDstu3Test;
|
||||||
import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil;
|
import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil;
|
||||||
import ca.uhn.fhir.jpa.subscription.SubscriptionTriggeringSvcImpl;
|
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.Create;
|
||||||
import ca.uhn.fhir.rest.annotation.ResourceParam;
|
import ca.uhn.fhir.rest.annotation.ResourceParam;
|
||||||
import ca.uhn.fhir.rest.annotation.Update;
|
import ca.uhn.fhir.rest.annotation.Update;
|
||||||
|
|
|
@ -33,6 +33,7 @@ public class RenameColumnTask extends BaseTableTask<RenameColumnTask> {
|
||||||
private static final Logger ourLog = LoggerFactory.getLogger(RenameColumnTask.class);
|
private static final Logger ourLog = LoggerFactory.getLogger(RenameColumnTask.class);
|
||||||
private String myOldName;
|
private String myOldName;
|
||||||
private String myNewName;
|
private String myNewName;
|
||||||
|
private boolean myAllowNeitherColumnToExist;
|
||||||
|
|
||||||
public void setOldName(String theOldName) {
|
public void setOldName(String theOldName) {
|
||||||
Validate.notBlank(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!");
|
throw new SQLException("Can not rename " + getTableName() + "." + myOldName + " to " + myNewName + " because both columns exist!");
|
||||||
}
|
}
|
||||||
if (!haveOldName && !haveNewName) {
|
if (!haveOldName && !haveNewName) {
|
||||||
|
if (isAllowNeitherColumnToExist()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
throw new SQLException("Can not rename " + getTableName() + "." + myOldName + " to " + myNewName + " because neither column exists!");
|
throw new SQLException("Can not rename " + getTableName() + "." + myOldName + " to " + myNewName + " because neither column exists!");
|
||||||
}
|
}
|
||||||
if (haveNewName) {
|
if (haveNewName) {
|
||||||
|
@ -89,4 +93,12 @@ public class RenameColumnTask extends BaseTableTask<RenameColumnTask> {
|
||||||
executeSql(getTableName(), sql);
|
executeSql(getTableName(), sql);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setAllowNeitherColumnToExist(boolean theAllowNeitherColumnToExist) {
|
||||||
|
myAllowNeitherColumnToExist = theAllowNeitherColumnToExist;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAllowNeitherColumnToExist() {
|
||||||
|
return myAllowNeitherColumnToExist;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,6 +80,16 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
|
||||||
.renameColumn("mySystemVersion", "SYSTEM_VERSION")
|
.renameColumn("mySystemVersion", "SYSTEM_VERSION")
|
||||||
.renameColumn("myValueSet", "VALUESET_URL");
|
.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
|
// TermValueSet
|
||||||
version.startSectionWithMessage("Processing table: TRM_VALUESET");
|
version.startSectionWithMessage("Processing table: TRM_VALUESET");
|
||||||
version.addIdGenerator("SEQ_VALUESET_PID");
|
version.addIdGenerator("SEQ_VALUESET_PID");
|
||||||
|
|
|
@ -168,10 +168,15 @@ public class BaseMigrationTasks<T extends Enum> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public BuilderWithTableName renameColumn(String theOldName, String theNewName) {
|
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();
|
RenameColumnTask task = new RenameColumnTask();
|
||||||
task.setTableName(myTableName);
|
task.setTableName(myTableName);
|
||||||
task.setOldName(theOldName);
|
task.setOldName(theOldName);
|
||||||
task.setNewName(theNewName);
|
task.setNewName(theNewName);
|
||||||
|
task.setAllowNeitherColumnToExist(theAllowNeitherColumnToExist);
|
||||||
addTask(task);
|
addTask(task);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,7 +45,7 @@ public class RenameColumnTaskTest extends BaseTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testNeitherColumnExists() throws SQLException {
|
public void testNeitherColumnExists() {
|
||||||
executeSql("create table SOMETABLE (PID bigint not null)");
|
executeSql("create table SOMETABLE (PID bigint not null)");
|
||||||
|
|
||||||
RenameColumnTask task = new RenameColumnTask();
|
RenameColumnTask task = new RenameColumnTask();
|
||||||
|
@ -65,7 +65,21 @@ public class RenameColumnTaskTest extends BaseTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@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)");
|
executeSql("create table SOMETABLE (PID bigint not null, PID2 bigint)");
|
||||||
|
|
||||||
RenameColumnTask task = new RenameColumnTask();
|
RenameColumnTask task = new RenameColumnTask();
|
||||||
|
|
|
@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||||
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
import ca.uhn.fhir.util.UrlUtil;
|
import ca.uhn.fhir.util.UrlUtil;
|
||||||
import com.google.common.base.Charsets;
|
import com.google.common.base.Charsets;
|
||||||
import com.google.common.hash.HashCode;
|
import com.google.common.hash.HashCode;
|
||||||
|
@ -64,7 +65,7 @@ public abstract class BaseResourceIndexedSearchParam extends BaseResourceIndex {
|
||||||
private Long myResourcePid;
|
private Long myResourcePid;
|
||||||
|
|
||||||
@Field()
|
@Field()
|
||||||
@Column(name = "RES_TYPE", nullable = false)
|
@Column(name = "RES_TYPE", nullable = false, length = Constants.MAX_RESOURCE_NAME_LENGTH)
|
||||||
private String myResourceType;
|
private String myResourceType;
|
||||||
|
|
||||||
@Field()
|
@Field()
|
||||||
|
|
|
@ -47,12 +47,13 @@ public class ResourceIndexedSearchParamUri extends BaseResourceIndexedSearchPara
|
||||||
/*
|
/*
|
||||||
* Note that MYSQL chokes on unique indexes for lengths > 255 so be careful here
|
* 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;
|
private static final long serialVersionUID = 1L;
|
||||||
@Column(name = "SP_URI", nullable = true, length = MAX_LENGTH)
|
@Column(name = "SP_URI", nullable = true, length = MAX_LENGTH)
|
||||||
@Field()
|
@Field()
|
||||||
public String myUri;
|
public String myUri;
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@SequenceGenerator(name = "SEQ_SPIDX_URI", sequenceName = "SEQ_SPIDX_URI")
|
@SequenceGenerator(name = "SEQ_SPIDX_URI", sequenceName = "SEQ_SPIDX_URI")
|
||||||
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SPIDX_URI")
|
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SPIDX_URI")
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package ca.uhn.fhir.jpa.util;
|
package ca.uhn.fhir.jpa.model.util;
|
||||||
|
|
||||||
/*-
|
/*-
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR JPA Server
|
* HAPI FHIR Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2019 University Health Network
|
* Copyright (C) 2014 - 2019 University Health Network
|
||||||
* %%
|
* %%
|
||||||
|
@ -147,4 +147,69 @@ public class JpaConstants {
|
||||||
* Operation name for the "$snapshot" operation
|
* Operation name for the "$snapshot" operation
|
||||||
*/
|
*/
|
||||||
public static final String OPERATION_SNAPSHOT = "$snapshot";
|
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";
|
||||||
|
|
||||||
}
|
}
|
|
@ -61,7 +61,11 @@ public class Retrier<T> {
|
||||||
@Override
|
@Override
|
||||||
public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
|
public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
|
||||||
super.onError(context, callback, 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);
|
myRetryTemplate.registerListener(listener);
|
||||||
|
|
|
@ -22,6 +22,7 @@ package ca.uhn.fhir.jpa.subscription.module.cache;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.ConfigurationException;
|
import ca.uhn.fhir.context.ConfigurationException;
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.jpa.model.util.JpaConstants;
|
||||||
import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription;
|
import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription;
|
||||||
import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscriptionChannelType;
|
import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscriptionChannelType;
|
||||||
import ca.uhn.fhir.jpa.subscription.module.matcher.SubscriptionMatchingStrategy;
|
import ca.uhn.fhir.jpa.subscription.module.matcher.SubscriptionMatchingStrategy;
|
||||||
|
@ -111,8 +112,8 @@ public class SubscriptionCanonicalizer<S extends IBaseResource> {
|
||||||
String subjectTemplate;
|
String subjectTemplate;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
from = subscription.getChannel().getExtensionString(SubscriptionConstants.EXT_SUBSCRIPTION_EMAIL_FROM);
|
from = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_EMAIL_FROM);
|
||||||
subjectTemplate = subscription.getChannel().getExtensionString(SubscriptionConstants.EXT_SUBSCRIPTION_SUBJECT_TEMPLATE);
|
subjectTemplate = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_SUBJECT_TEMPLATE);
|
||||||
} catch (FHIRException theE) {
|
} catch (FHIRException theE) {
|
||||||
throw new ConfigurationException("Failed to extract subscription extension(s): " + theE.getMessage(), 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 stripVersionIds;
|
||||||
String deliverLatestVersion;
|
String deliverLatestVersion;
|
||||||
try {
|
try {
|
||||||
stripVersionIds = subscription.getChannel().getExtensionString(SubscriptionConstants.EXT_SUBSCRIPTION_RESTHOOK_STRIP_VERSION_IDS);
|
stripVersionIds = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_RESTHOOK_STRIP_VERSION_IDS);
|
||||||
deliverLatestVersion = subscription.getChannel().getExtensionString(SubscriptionConstants.EXT_SUBSCRIPTION_RESTHOOK_DELIVER_LATEST_VERSION);
|
deliverLatestVersion = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_RESTHOOK_DELIVER_LATEST_VERSION);
|
||||||
} catch (FHIRException theE) {
|
} catch (FHIRException theE) {
|
||||||
throw new ConfigurationException("Failed to extract subscription extension(s): " + theE.getMessage(), 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 from;
|
||||||
String subjectTemplate;
|
String subjectTemplate;
|
||||||
try {
|
try {
|
||||||
from = subscription.getChannel().getExtensionString(SubscriptionConstants.EXT_SUBSCRIPTION_EMAIL_FROM);
|
from = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_EMAIL_FROM);
|
||||||
subjectTemplate = subscription.getChannel().getExtensionString(SubscriptionConstants.EXT_SUBSCRIPTION_SUBJECT_TEMPLATE);
|
subjectTemplate = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_SUBJECT_TEMPLATE);
|
||||||
} catch (FHIRException theE) {
|
} catch (FHIRException theE) {
|
||||||
throw new ConfigurationException("Failed to extract subscription extension(s): " + theE.getMessage(), 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 stripVersionIds;
|
||||||
String deliverLatestVersion;
|
String deliverLatestVersion;
|
||||||
try {
|
try {
|
||||||
stripVersionIds = subscription.getChannel().getExtensionString(SubscriptionConstants.EXT_SUBSCRIPTION_RESTHOOK_STRIP_VERSION_IDS);
|
stripVersionIds = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_RESTHOOK_STRIP_VERSION_IDS);
|
||||||
deliverLatestVersion = subscription.getChannel().getExtensionString(SubscriptionConstants.EXT_SUBSCRIPTION_RESTHOOK_DELIVER_LATEST_VERSION);
|
deliverLatestVersion = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_RESTHOOK_DELIVER_LATEST_VERSION);
|
||||||
} catch (FHIRException theE) {
|
} catch (FHIRException theE) {
|
||||||
throw new ConfigurationException("Failed to extract subscription extension(s): " + theE.getMessage(), theE);
|
throw new ConfigurationException("Failed to extract subscription extension(s): " + theE.getMessage(), theE);
|
||||||
}
|
}
|
||||||
|
@ -259,7 +260,7 @@ public class SubscriptionCanonicalizer<S extends IBaseResource> {
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalStateException("Unknown " + SubscriptionMatchingStrategy.class.getSimpleName() + ": " + theStrategy);
|
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) {
|
public String getSubscriptionStatus(IBaseResource theSubscription) {
|
||||||
|
|
|
@ -24,57 +24,6 @@ import org.hl7.fhir.dstu2.model.Subscription;
|
||||||
|
|
||||||
public class SubscriptionConstants {
|
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
|
* The number of threads used in subscription channel processing
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhirtest.interceptor;
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.provider.BaseJpaSystemProvider;
|
import ca.uhn.fhir.jpa.provider.BaseJpaSystemProvider;
|
||||||
import ca.uhn.fhir.jpa.provider.BaseTerminologyUploaderProvider;
|
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.api.server.RequestDetails;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.ForbiddenOperationException;
|
import ca.uhn.fhir.rest.server.exceptions.ForbiddenOperationException;
|
||||||
import ca.uhn.fhir.rest.server.interceptor.auth.AuthorizationInterceptor;
|
import ca.uhn.fhir.rest.server.interceptor.auth.AuthorizationInterceptor;
|
||||||
|
|
|
@ -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
|
* if we can, since that can include the resource version which is nice
|
||||||
* for the user.
|
* for the user.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -21,6 +21,7 @@ package ca.uhn.fhir.rest.server.interceptor;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.interceptor.api.Interceptor;
|
||||||
import ca.uhn.fhir.parser.IParser;
|
import ca.uhn.fhir.parser.IParser;
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
|
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
|
* 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}.
|
* request with an {@link UnprocessableEntityException HTTP 422 Unprocessable Entity}.
|
||||||
*/
|
*/
|
||||||
abstract class BaseValidatingInterceptor<T> extends InterceptorAdapter {
|
@Interceptor
|
||||||
|
abstract class BaseValidatingInterceptor<T> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default value:<br/>
|
* Default value:<br/>
|
||||||
|
|
|
@ -27,6 +27,8 @@ import java.nio.charset.Charset;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
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.IBaseOperationOutcome;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
|
||||||
|
@ -67,7 +69,7 @@ public class RequestValidatingInterceptor extends BaseValidatingInterceptor<Stri
|
||||||
return theValidator.validateWithResult(theRequest);
|
return theValidator.validateWithResult(theRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Hook(Pointcut.SERVER_INCOMING_REQUEST_POST_PROCESSED)
|
||||||
public boolean incomingRequestPostProcessed(RequestDetails theRequestDetails, HttpServletRequest theRequest, HttpServletResponse theResponse) throws AuthenticationException {
|
public boolean incomingRequestPostProcessed(RequestDetails theRequestDetails, HttpServletRequest theRequest, HttpServletResponse theResponse) throws AuthenticationException {
|
||||||
EncodingEnum encoding = RestfulServerUtils.determineRequestEncodingNoDefault(theRequestDetails);
|
EncodingEnum encoding = RestfulServerUtils.determineRequestEncodingNoDefault(theRequestDetails);
|
||||||
if (encoding == null) {
|
if (encoding == null) {
|
||||||
|
@ -102,7 +104,7 @@ public class RequestValidatingInterceptor extends BaseValidatingInterceptor<Stri
|
||||||
return myAddValidationResultsToResponseOperationOutcome;
|
return myAddValidationResultsToResponseOperationOutcome;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Hook(Pointcut.SERVER_OUTGOING_RESPONSE)
|
||||||
public boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject) {
|
public boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject) {
|
||||||
if (myAddValidationResultsToResponseOperationOutcome) {
|
if (myAddValidationResultsToResponseOperationOutcome) {
|
||||||
if (theResponseObject instanceof IBaseOperationOutcome) {
|
if (theResponseObject instanceof IBaseOperationOutcome) {
|
||||||
|
|
|
@ -23,6 +23,8 @@ package ca.uhn.fhir.rest.server.interceptor;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
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.apache.commons.lang3.Validate;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
|
||||||
|
@ -54,7 +56,7 @@ public class ResponseValidatingInterceptor extends BaseValidatingInterceptor<IBa
|
||||||
public void addExcludeOperationType(RestOperationTypeEnum theOperationType) {
|
public void addExcludeOperationType(RestOperationTypeEnum theOperationType) {
|
||||||
Validate.notNull(theOperationType, "theOperationType must not be null");
|
Validate.notNull(theOperationType, "theOperationType must not be null");
|
||||||
if (myExcludeOperationTypes == null) {
|
if (myExcludeOperationTypes == null) {
|
||||||
myExcludeOperationTypes = new HashSet<RestOperationTypeEnum>();
|
myExcludeOperationTypes = new HashSet<>();
|
||||||
}
|
}
|
||||||
myExcludeOperationTypes.add(theOperationType);
|
myExcludeOperationTypes.add(theOperationType);
|
||||||
}
|
}
|
||||||
|
@ -64,7 +66,7 @@ public class ResponseValidatingInterceptor extends BaseValidatingInterceptor<IBa
|
||||||
return theValidator.validateWithResult(theRequest);
|
return theValidator.validateWithResult(theRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Hook(Pointcut.SERVER_OUTGOING_RESPONSE)
|
||||||
public boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject) {
|
public boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject) {
|
||||||
RestOperationTypeEnum operationType = theRequestDetails.getRestOperationType();
|
RestOperationTypeEnum operationType = theRequestDetails.getRestOperationType();
|
||||||
if (operationType != null && myExcludeOperationTypes != null && myExcludeOperationTypes.contains(operationType)) {
|
if (operationType != null && myExcludeOperationTypes != null && myExcludeOperationTypes.contains(operationType)) {
|
||||||
|
|
|
@ -300,8 +300,12 @@ public class ConsentInterceptor {
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isRequestAuthorized(RequestDetails theRequestDetails) {
|
private boolean isRequestAuthorized(RequestDetails theRequestDetails) {
|
||||||
Object authorizedObj = theRequestDetails.getUserData().get(myRequestAuthorizedKey);
|
boolean retVal = false;
|
||||||
return Boolean.TRUE.equals(authorizedObj);
|
if (theRequestDetails != null) {
|
||||||
|
Object authorizedObj = theRequestDetails.getUserData().get(myRequestAuthorizedKey);
|
||||||
|
retVal = Boolean.TRUE.equals(authorizedObj);
|
||||||
|
}
|
||||||
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
|
|
|
@ -89,6 +89,8 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi
|
||||||
myMethodReturnType = MethodReturnTypeEnum.BUNDLE_PROVIDER;
|
myMethodReturnType = MethodReturnTypeEnum.BUNDLE_PROVIDER;
|
||||||
} else if (MethodOutcome.class.isAssignableFrom(methodReturnType)) {
|
} else if (MethodOutcome.class.isAssignableFrom(methodReturnType)) {
|
||||||
myMethodReturnType = MethodReturnTypeEnum.METHOD_OUTCOME;
|
myMethodReturnType = MethodReturnTypeEnum.METHOD_OUTCOME;
|
||||||
|
} else if (void.class.equals(methodReturnType)) {
|
||||||
|
myMethodReturnType = MethodReturnTypeEnum.VOID;
|
||||||
} else {
|
} else {
|
||||||
throw new ConfigurationException(
|
throw new ConfigurationException(
|
||||||
"Invalid return type '" + methodReturnType.getCanonicalName() + "' on method '" + theMethod.getName() + "' on type: " + theMethod.getDeclaringClass().getCanonicalName());
|
"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) {
|
public IBaseResource doInvokeServer(IRestfulServer<?> theServer, RequestDetails theRequest) {
|
||||||
Object[] params = createMethodParams(theRequest);
|
Object[] params = createMethodParams(theRequest);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Object resultObj = invokeServer(theServer, theRequest, params);
|
Object resultObj = invokeServer(theServer, theRequest, params);
|
||||||
|
if (resultObj == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
Integer count = RestfulServerUtils.extractCountParameter(theRequest);
|
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 {
|
public Object invokeServer(IRestfulServer<?> theServer, RequestDetails theRequest) throws BaseServerResponseException, IOException {
|
||||||
|
|
||||||
IBaseResource response = doInvokeServer(theServer, theRequest);
|
IBaseResource response = doInvokeServer(theServer, theRequest);
|
||||||
|
if (response == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
Set<SummaryEnum> summaryMode = RestfulServerUtils.determineSummaryMode(theRequest);
|
Set<SummaryEnum> summaryMode = RestfulServerUtils.determineSummaryMode(theRequest);
|
||||||
|
|
||||||
|
@ -407,6 +413,7 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi
|
||||||
BUNDLE_RESOURCE,
|
BUNDLE_RESOURCE,
|
||||||
LIST_OF_RESOURCES,
|
LIST_OF_RESOURCES,
|
||||||
METHOD_OUTCOME,
|
METHOD_OUTCOME,
|
||||||
|
VOID,
|
||||||
RESOURCE
|
RESOURCE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -61,16 +61,19 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
|
||||||
private final String myName;
|
private final String myName;
|
||||||
private final RestOperationTypeEnum myOtherOperationType;
|
private final RestOperationTypeEnum myOtherOperationType;
|
||||||
private final ReturnTypeEnum myReturnType;
|
private final ReturnTypeEnum myReturnType;
|
||||||
|
private boolean myGlobal;
|
||||||
private BundleTypeEnum myBundleType;
|
private BundleTypeEnum myBundleType;
|
||||||
private boolean myCanOperateAtInstanceLevel;
|
private boolean myCanOperateAtInstanceLevel;
|
||||||
private boolean myCanOperateAtServerLevel;
|
private boolean myCanOperateAtServerLevel;
|
||||||
private boolean myCanOperateAtTypeLevel;
|
private boolean myCanOperateAtTypeLevel;
|
||||||
private String myDescription;
|
private String myDescription;
|
||||||
private List<ReturnType> myReturnParams;
|
private List<ReturnType> myReturnParams;
|
||||||
|
private boolean myManualRequestMode;
|
||||||
|
private boolean myManualResponseMode;
|
||||||
|
|
||||||
protected OperationMethodBinding(Class<?> theReturnResourceType, Class<? extends IBaseResource> theReturnTypeFromRp, Method theMethod, FhirContext theContext, Object theProvider,
|
protected OperationMethodBinding(Class<?> theReturnResourceType, Class<? extends IBaseResource> theReturnTypeFromRp, Method theMethod, FhirContext theContext, Object theProvider,
|
||||||
boolean theIdempotent, String theOperationName, Class<? extends IBaseResource> theOperationType,
|
boolean theIdempotent, String theOperationName, Class<? extends IBaseResource> theOperationType,
|
||||||
OperationParam[] theReturnParams, BundleTypeEnum theBundleType) {
|
OperationParam[] theReturnParams, BundleTypeEnum theBundleType) {
|
||||||
super(theReturnResourceType, theMethod, theContext, theProvider);
|
super(theReturnResourceType, theMethod, theContext, theProvider);
|
||||||
|
|
||||||
myBundleType = theBundleType;
|
myBundleType = theBundleType;
|
||||||
|
@ -89,7 +92,7 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
|
||||||
|
|
||||||
if (isBlank(theOperationName)) {
|
if (isBlank(theOperationName)) {
|
||||||
throw new ConfigurationException("Method '" + theMethod.getName() + "' on type " + theMethod.getDeclaringClass().getName() + " is annotated with @" + Operation.class.getSimpleName()
|
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) {
|
if (theOperationName.startsWith("$") == false) {
|
||||||
theOperationName = "$" + theOperationName;
|
theOperationName = "$" + theOperationName;
|
||||||
|
@ -99,10 +102,10 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
|
||||||
if (theReturnTypeFromRp != null) {
|
if (theReturnTypeFromRp != null) {
|
||||||
setResourceName(theContext.getResourceDefinition(theReturnTypeFromRp).getName());
|
setResourceName(theContext.getResourceDefinition(theReturnTypeFromRp).getName());
|
||||||
} else if (Modifier.isAbstract(theOperationType.getModifiers()) == false) {
|
} else if (Modifier.isAbstract(theOperationType.getModifiers()) == false) {
|
||||||
setResourceName(theContext.getResourceDefinition(theOperationType).getName());
|
setResourceName(theContext.getResourceDefinition(theOperationType).getName());
|
||||||
} else {
|
} else {
|
||||||
setResourceName(null);
|
setResourceName(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (theMethod.getReturnType().equals(IBundleProvider.class)) {
|
if (theMethod.getReturnType().equals(IBundleProvider.class)) {
|
||||||
myReturnType = ReturnTypeEnum.BUNDLE;
|
myReturnType = ReturnTypeEnum.BUNDLE;
|
||||||
|
@ -110,24 +113,24 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
|
||||||
myReturnType = ReturnTypeEnum.RESOURCE;
|
myReturnType = ReturnTypeEnum.RESOURCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
myIdParamIndex = ParameterUtil.findIdParameterIndex(theMethod, getContext());
|
myIdParamIndex = ParameterUtil.findIdParameterIndex(theMethod, getContext());
|
||||||
if (getResourceName() == null) {
|
if (getResourceName() == null) {
|
||||||
myOtherOperationType = RestOperationTypeEnum.EXTENDED_OPERATION_SERVER;
|
myOtherOperationType = RestOperationTypeEnum.EXTENDED_OPERATION_SERVER;
|
||||||
myCanOperateAtServerLevel = true;
|
myCanOperateAtServerLevel = true;
|
||||||
if (myIdParamIndex != null) {
|
if (myIdParamIndex != null) {
|
||||||
myCanOperateAtInstanceLevel = true;
|
myCanOperateAtInstanceLevel = true;
|
||||||
}
|
}
|
||||||
} else if (myIdParamIndex == null) {
|
} else if (myIdParamIndex == null) {
|
||||||
myOtherOperationType = RestOperationTypeEnum.EXTENDED_OPERATION_TYPE;
|
myOtherOperationType = RestOperationTypeEnum.EXTENDED_OPERATION_TYPE;
|
||||||
myCanOperateAtTypeLevel = true;
|
myCanOperateAtTypeLevel = true;
|
||||||
} else {
|
} else {
|
||||||
myOtherOperationType = RestOperationTypeEnum.EXTENDED_OPERATION_INSTANCE;
|
myOtherOperationType = RestOperationTypeEnum.EXTENDED_OPERATION_INSTANCE;
|
||||||
myCanOperateAtInstanceLevel = true;
|
myCanOperateAtInstanceLevel = true;
|
||||||
for (Annotation next : theMethod.getParameterAnnotations()[myIdParamIndex]) {
|
for (Annotation next : theMethod.getParameterAnnotations()[myIdParamIndex]) {
|
||||||
if (next instanceof IdParam) {
|
if (next instanceof IdParam) {
|
||||||
myCanOperateAtTypeLevel = ((IdParam) next).optional() == true;
|
myCanOperateAtTypeLevel = ((IdParam) next).optional() == true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
myReturnParams = new ArrayList<>();
|
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,
|
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(),
|
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() {
|
public String getDescription() {
|
||||||
|
@ -206,7 +222,9 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
|
||||||
|
|
||||||
if (getResourceName() == null) {
|
if (getResourceName() == null) {
|
||||||
if (isNotBlank(theRequest.getResourceName())) {
|
if (isNotBlank(theRequest.getResourceName())) {
|
||||||
return false;
|
if (!isGlobalMethod()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -222,12 +240,12 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
|
||||||
|
|
||||||
boolean requestHasId = theRequest.getId() != null;
|
boolean requestHasId = theRequest.getId() != null;
|
||||||
if (requestHasId) {
|
if (requestHasId) {
|
||||||
return myCanOperateAtInstanceLevel;
|
return myCanOperateAtInstanceLevel;
|
||||||
}
|
}
|
||||||
if (isNotBlank(theRequest.getResourceName())) {
|
if (isNotBlank(theRequest.getResourceName())) {
|
||||||
return myCanOperateAtTypeLevel;
|
return myCanOperateAtTypeLevel;
|
||||||
}
|
}
|
||||||
return myCanOperateAtServerLevel;
|
return myCanOperateAtServerLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -256,7 +280,7 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object invokeServer(IRestfulServer<?> theServer, RequestDetails theRequest) throws BaseServerResponseException, IOException {
|
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);
|
IBaseResource requestContents = ResourceParameter.loadResourceFromRequest(theRequest, this, null);
|
||||||
theRequest.getUserData().put(OperationParameter.REQUEST_CONTENTS_USERDATA_KEY, requestContents);
|
theRequest.getUserData().put(OperationParameter.REQUEST_CONTENTS_USERDATA_KEY, requestContents);
|
||||||
}
|
}
|
||||||
|
@ -286,6 +310,10 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
|
||||||
}
|
}
|
||||||
|
|
||||||
Object response = invokeServerMethod(theServer, theRequest, theMethodParams);
|
Object response = invokeServerMethod(theServer, theRequest, theMethodParams);
|
||||||
|
if (myManualResponseMode) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
IBundleProvider retVal = toResourceList(response);
|
IBundleProvider retVal = toResourceList(response);
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
@ -316,6 +344,10 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isManualRequestMode() {
|
||||||
|
return myManualRequestMode;
|
||||||
|
}
|
||||||
|
|
||||||
public static class ReturnType {
|
public static class ReturnType {
|
||||||
private int myMax;
|
private int myMax;
|
||||||
private int myMin;
|
private int myMin;
|
||||||
|
|
|
@ -210,7 +210,9 @@ public class OperationParameter implements IParameter {
|
||||||
public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding<?> theMethodBinding) throws InternalErrorException, InvalidRequestException {
|
public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding<?> theMethodBinding) throws InternalErrorException, InvalidRequestException {
|
||||||
List<Object> matchingParamValues = new ArrayList<Object>();
|
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);
|
translateQueryParametersIntoServerArgumentForGet(theRequest, matchingParamValues);
|
||||||
} else {
|
} else {
|
||||||
translateQueryParametersIntoServerArgumentForPost(theRequest, matchingParamValues);
|
translateQueryParametersIntoServerArgumentForPost(theRequest, matchingParamValues);
|
||||||
|
|
|
@ -7,9 +7,14 @@ import ca.uhn.fhir.rest.annotation.OperationParam;
|
||||||
import ca.uhn.fhir.rest.annotation.Read;
|
import ca.uhn.fhir.rest.annotation.Read;
|
||||||
import ca.uhn.fhir.rest.api.Constants;
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
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.IBundleProvider;
|
||||||
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
import ca.uhn.fhir.rest.client.api.IGenericClient;
|
import ca.uhn.fhir.rest.client.api.IGenericClient;
|
||||||
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
|
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 ca.uhn.fhir.util.TestUtil;
|
||||||
import com.google.common.base.Charsets;
|
import com.google.common.base.Charsets;
|
||||||
import org.apache.commons.io.IOUtils;
|
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.CloseableHttpResponse;
|
||||||
import org.apache.http.client.methods.HttpGet;
|
import org.apache.http.client.methods.HttpGet;
|
||||||
import org.apache.http.client.methods.HttpPost;
|
import org.apache.http.client.methods.HttpPost;
|
||||||
|
import org.apache.http.entity.ByteArrayEntity;
|
||||||
import org.apache.http.entity.ContentType;
|
import org.apache.http.entity.ContentType;
|
||||||
import org.apache.http.entity.StringEntity;
|
import org.apache.http.entity.StringEntity;
|
||||||
import org.apache.http.impl.client.CloseableHttpClient;
|
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.ServletHandler;
|
||||||
import org.eclipse.jetty.servlet.ServletHolder;
|
import org.eclipse.jetty.servlet.ServletHolder;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
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.*;
|
||||||
import org.hl7.fhir.r4.model.OperationDefinition.OperationParameterUse;
|
import org.hl7.fhir.r4.model.OperationDefinition.OperationParameterUse;
|
||||||
import org.junit.AfterClass;
|
import org.junit.AfterClass;
|
||||||
|
@ -33,16 +41,18 @@ import org.junit.Before;
|
||||||
import org.junit.BeforeClass;
|
import org.junit.BeforeClass;
|
||||||
import org.junit.Test;
|
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.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||||
import static org.hamcrest.Matchers.*;
|
import static org.hamcrest.Matchers.*;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
import ca.uhn.fhir.test.utilities.JettyUtil;
|
|
||||||
|
|
||||||
public class OperationServerR4Test {
|
public class OperationServerR4Test {
|
||||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(OperationServerR4Test.class);
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(OperationServerR4Test.class);
|
||||||
private static final String TEXT_HTML = "text/html";
|
private static final String TEXT_HTML = "text/html";
|
||||||
|
@ -57,6 +67,7 @@ public class OperationServerR4Test {
|
||||||
private static UnsignedIntType ourLastParamUnsignedInt1;
|
private static UnsignedIntType ourLastParamUnsignedInt1;
|
||||||
private static int ourPort;
|
private static int ourPort;
|
||||||
private static Server ourServer;
|
private static Server ourServer;
|
||||||
|
private static IBaseResource ourNextResponse;
|
||||||
private IGenericClient myFhirClient;
|
private IGenericClient myFhirClient;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
|
@ -69,11 +80,11 @@ public class OperationServerR4Test {
|
||||||
ourLastId = null;
|
ourLastId = null;
|
||||||
ourLastMethod = "";
|
ourLastMethod = "";
|
||||||
ourNextResponse = null;
|
ourNextResponse = null;
|
||||||
|
ourLastRestOperation = null;
|
||||||
|
|
||||||
myFhirClient = ourCtx.newRestfulGenericClient("http://localhost:" + ourPort);
|
myFhirClient = ourCtx.newRestfulGenericClient("http://localhost:" + ourPort);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testConformance() {
|
public void testConformance() {
|
||||||
LoggingInterceptor loggingInterceptor = new LoggingInterceptor();
|
LoggingInterceptor loggingInterceptor = new LoggingInterceptor();
|
||||||
|
@ -150,7 +161,7 @@ public class OperationServerR4Test {
|
||||||
bundle.addEntry().setResource(patient);
|
bundle.addEntry().setResource(patient);
|
||||||
|
|
||||||
HttpGet httpPost = new HttpGet("http://localhost:" + ourPort + "/Patient/$OP_TYPE_RETURNING_BUNDLE"
|
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)) {
|
try (CloseableHttpResponse status = ourClient.execute(httpPost)) {
|
||||||
|
|
||||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
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
|
@Test
|
||||||
public void testInstanceEverythingGet() throws Exception {
|
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
|
@Test
|
||||||
public void testInstanceEverythingHapiClient() {
|
public void testInstanceEverythingHapiClient() {
|
||||||
ourCtx.newRestfulGenericClient("http://localhost:" + ourPort).operation().onInstance(new IdType("Patient/123")).named("$everything").withParameters(new Parameters()).execute();
|
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
|
@Test
|
||||||
public void testOperationCantUseGetIfItIsntIdempotent() throws Exception {
|
public void testOperationCantUseGetIfItIsntIdempotent() throws Exception {
|
||||||
HttpGet httpPost = new HttpGet("http://localhost:" + ourPort + "/Patient/123/$OP_INSTANCE");
|
HttpGet httpPost = new HttpGet("http://localhost:" + ourPort + "/Patient/123/$OP_INSTANCE");
|
||||||
|
@ -439,7 +525,7 @@ public class OperationServerR4Test {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testOperationWithBundleProviderResponse() throws Exception {
|
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);
|
HttpResponse status = ourClient.execute(httpPost);
|
||||||
|
|
||||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
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"));
|
assertThat(response, containsString("Can not invoke operation $OP_TYPE using HTTP GET because parameter PARAM2 is not a primitive datatype"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testOperationWithListParam() throws Exception {
|
public void testOperationWithListParam() throws Exception {
|
||||||
Parameters p = new Parameters();
|
Parameters p = new Parameters();
|
||||||
|
@ -618,7 +703,6 @@ public class OperationServerR4Test {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IBaseResource ourNextResponse;
|
|
||||||
public static class PatientProvider implements IResourceProvider {
|
public static class PatientProvider implements IResourceProvider {
|
||||||
|
|
||||||
|
|
||||||
|
@ -750,6 +834,36 @@ public class OperationServerR4Test {
|
||||||
return new Bundle();
|
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
|
* 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 {
|
public static class PlainProvider {
|
||||||
|
|
||||||
@Operation(name = "$OP_INSTANCE_BUNDLE_PROVIDER", idempotent = true)
|
|
||||||
public IBundleProvider opInstanceReturnsBundleProvider() {
|
@Operation(name = "$OP_PLAIN_PROVIDER_ON_INSTANCE", idempotent = true, global = true)
|
||||||
ourLastMethod = "$OP_INSTANCE_BUNDLE_PROVIDER";
|
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>();
|
List<IBaseResource> resources = new ArrayList<IBaseResource>();
|
||||||
for (int i = 0; i < 100; i++) {
|
for (int i = 0; i < 100; i++) {
|
||||||
|
@ -780,7 +898,38 @@ public class OperationServerR4Test {
|
||||||
return new SimpleBundleProvider(resources);
|
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(
|
public Parameters opServer(
|
||||||
@OperationParam(name = "PARAM1") StringType theParam1,
|
@OperationParam(name = "PARAM1") StringType theParam1,
|
||||||
@OperationParam(name = "PARAM2") Patient theParam2
|
@OperationParam(name = "PARAM2") Patient theParam2
|
||||||
|
@ -859,13 +1008,14 @@ public class OperationServerR4Test {
|
||||||
servlet.setPagingProvider(new FifoMemoryPagingProvider(10).setDefaultPageSize(2));
|
servlet.setPagingProvider(new FifoMemoryPagingProvider(10).setDefaultPageSize(2));
|
||||||
|
|
||||||
servlet.setFhirContext(ourCtx);
|
servlet.setFhirContext(ourCtx);
|
||||||
servlet.setResourceProviders(new PatientProvider());
|
PlainProvider plainProvider = new PlainProvider();
|
||||||
servlet.setPlainProviders(new PlainProvider());
|
PatientProvider patientProvider = new PatientProvider();
|
||||||
|
servlet.registerProviders(patientProvider, plainProvider);
|
||||||
ServletHolder servletHolder = new ServletHolder(servlet);
|
ServletHolder servletHolder = new ServletHolder(servlet);
|
||||||
proxyHandler.addServletWithMapping(servletHolder, "/*");
|
proxyHandler.addServletWithMapping(servletHolder, "/*");
|
||||||
ourServer.setHandler(proxyHandler);
|
ourServer.setHandler(proxyHandler);
|
||||||
JettyUtil.startServer(ourServer);
|
JettyUtil.startServer(ourServer);
|
||||||
ourPort = JettyUtil.getPortForStartedServer(ourServer);
|
ourPort = JettyUtil.getPortForStartedServer(ourServer);
|
||||||
|
|
||||||
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
|
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
|
||||||
HttpClientBuilder builder = HttpClientBuilder.create();
|
HttpClientBuilder builder = HttpClientBuilder.create();
|
||||||
|
|
|
@ -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
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue