All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dev.dsf.fhir.webservice.jaxrs.BinaryServiceJaxrs Maven / Gradle / Ivy

package dev.dsf.fhir.webservice.jaxrs;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

import org.hl7.fhir.r4.model.Binary;
import org.hl7.fhir.r4.model.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ca.uhn.fhir.rest.api.Constants;
import dev.dsf.fhir.help.ParameterConverter;
import dev.dsf.fhir.help.ResponseGenerator;
import dev.dsf.fhir.webservice.specification.BinaryService;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.EntityTag;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.ResponseBuilder;
import jakarta.ws.rs.core.Response.Status;
import jakarta.ws.rs.core.UriInfo;

@Path(BinaryServiceJaxrs.PATH)
public class BinaryServiceJaxrs extends AbstractResourceServiceJaxrs implements BinaryService
{
	public static final String PATH = "Binary";

	private static final Logger logger = LoggerFactory.getLogger(BinaryServiceJaxrs.class);

	private final String[] FHIR_MEDIA_TYPES = { Constants.CT_FHIR_XML_NEW, Constants.CT_FHIR_JSON_NEW,
			Constants.CT_FHIR_XML, Constants.CT_FHIR_JSON };
	private final ParameterConverter parameterConverter;

	public BinaryServiceJaxrs(BinaryService delegate, ParameterConverter parameterConverter)
	{
		super(delegate);

		this.parameterConverter = parameterConverter;
	}

	@Override
	public void afterPropertiesSet() throws Exception
	{
		super.afterPropertiesSet();

		Objects.requireNonNull(parameterConverter, "parameterConverter");
	}

	@POST
	@Consumes
	@Produces({ Constants.CT_FHIR_XML, Constants.CT_FHIR_XML_NEW, MediaType.APPLICATION_XML, Constants.CT_FHIR_JSON,
			Constants.CT_FHIR_JSON_NEW, MediaType.APPLICATION_JSON, MediaType.TEXT_HTML })
	@Override
	public Response create(InputStream in, @Context UriInfo uri, @Context HttpHeaders headers)
	{
		try (in)
		{
			String securityContext = getSecurityContext(headers);
			String contentType = getContentType(headers);
			byte[] content = in.readAllBytes();

			Binary resource = createBinary(contentType, content, securityContext);
			return delegate.create(resource, uri, headers);
		}
		catch (IOException e)
		{
			throw new WebApplicationException(e);
		}
	}

	private Binary createBinary(String contentType, byte[] content, String securityContextReference)
	{
		Binary resource = new Binary();
		resource.setContentType(contentType);
		resource.setContent(content);
		resource.setSecurityContext(new Reference(securityContextReference));
		return resource;
	}

	private String getSecurityContext(HttpHeaders headers)
	{
		return getHeaderValueOrThrowBadRequest(headers, Constants.HEADER_X_SECURITY_CONTEXT);
	}

	private String getContentType(HttpHeaders headers)
	{
		return getHeaderValueOrThrowBadRequest(headers, HttpHeaders.CONTENT_TYPE);
	}

	private String getHeaderValueOrThrowBadRequest(HttpHeaders headers, String header)
	{
		List headerValue = headers.getRequestHeader(header);
		if (headerValue != null && headerValue.size() == 1)
		{
			String hV0 = headerValue.get(0);
			if (hV0 != null && !hV0.isBlank())
				return hV0;
			else
			{
				logger.warn("{} header found, no value, sending {}", header, Status.BAD_REQUEST);
				throw new WebApplicationException(Status.BAD_REQUEST);
			}
		}
		else if (headerValue != null && headerValue.size() > 1)
		{
			logger.warn("{} header found, more than one value, sending {}", header, Status.BAD_REQUEST);
			throw new WebApplicationException(Status.BAD_REQUEST);
		}

		headerValue = headers.getRequestHeader(header.toLowerCase());
		if (headerValue != null && headerValue.size() == 1)
		{
			String hV0 = headerValue.get(0);
			if (hV0 != null && !hV0.isBlank())
				return hV0;
			else
			{
				logger.warn("{} header found, no value, sending {}", header, Status.BAD_REQUEST);
				throw new WebApplicationException(Status.BAD_REQUEST);
			}
		}
		else if (headerValue != null && headerValue.size() > 1)
		{
			logger.warn("{} header found, more than one value, sending {}", header, Status.BAD_REQUEST);
			throw new WebApplicationException(Status.BAD_REQUEST);
		}

		logger.warn("{} header not found, sending {}", header, Status.BAD_REQUEST);
		throw new WebApplicationException(Status.BAD_REQUEST);
	}

	@GET
	@Path("/{id}")
	@Produces
	@Override
	public Response read(@PathParam("id") String id, @Context UriInfo uri, @Context HttpHeaders headers)
	{
		Response read = super.read(id, uri, headers);

		if (read.getEntity() instanceof Binary binary && !isValidFhirRequest(uri, headers))
		{
			if (mediaTypeMatches(headers, binary))
				return toStream(binary);
			else
				return Response.status(Status.NOT_ACCEPTABLE).build();
		}
		else
			return read;
	}

	private boolean mediaTypeMatches(HttpHeaders headers, Binary binary)
	{
		MediaType binaryMediaType = MediaType.valueOf(binary.getContentType());
		return headers.getAcceptableMediaTypes() != null && headers.getAcceptableMediaTypes().stream()
				.anyMatch(acceptType -> acceptType.isCompatible(binaryMediaType));
	}

	private Response toStream(Binary binary)
	{
		String contentType = binary.getContentType();
		byte[] content = binary.getContent();

		ResponseBuilder b = Response.status(Status.OK).entity(new ByteArrayInputStream(content));
		b = b.type(contentType);

		if (binary.getMeta() != null && binary.getMeta().getLastUpdated() != null
				&& binary.getMeta().getVersionId() != null)
		{
			b = b.lastModified(binary.getMeta().getLastUpdated());
			b = b.tag(new EntityTag(binary.getMeta().getVersionId(), true));
		}

		if (binary.hasSecurityContext() && binary.getSecurityContext().hasReference())
		{
			// Not setting header for logical references
			b.header(Constants.HEADER_X_SECURITY_CONTEXT, binary.getSecurityContext().getReference());
		}

		b = b.cacheControl(ResponseGenerator.PRIVATE_NO_CACHE_NO_TRANSFORM);

		return b.build();
	}

	@GET
	@Path("/{id}/_history/{version}")
	@Produces
	@Override
	public Response vread(@PathParam("id") String id, @PathParam("version") long version, @Context UriInfo uri,
			@Context HttpHeaders headers)
	{
		Response read = super.vread(id, version, uri, headers);

		if (read.getEntity() instanceof Binary binary && !isValidFhirRequest(uri, headers))
		{
			if (mediaTypeMatches(headers, binary))
				return toStream(binary);
			else
				return Response.status(Status.NOT_ACCEPTABLE).build();
		}
		else
			return read;
	}

	private boolean isValidFhirRequest(UriInfo uri, HttpHeaders headers)
	{
		// _format parameter override present and valid
		if (uri.getQueryParameters().containsKey(Constants.PARAM_FORMAT))
		{
			parameterConverter.getMediaTypeThrowIfNotSupported(uri, headers);
			return true;
		}
		else
		{
			List types = headers.getAcceptableMediaTypes();
			MediaType accept = types == null ? null : types.get(0);

			// accept header is FHIR mime-type
			return Arrays.stream(FHIR_MEDIA_TYPES).anyMatch(f -> f.equals(accept.toString()));
		}
	}

	@PUT
	@Path("/{id}")
	@Consumes
	@Produces({ Constants.CT_FHIR_XML, Constants.CT_FHIR_XML_NEW, MediaType.APPLICATION_XML, Constants.CT_FHIR_JSON,
			Constants.CT_FHIR_JSON_NEW, MediaType.APPLICATION_JSON, MediaType.TEXT_HTML })
	@Override
	public Response update(@PathParam("id") String id, InputStream in, @Context UriInfo uri,
			@Context HttpHeaders headers)
	{
		try (in)
		{
			String securityContext = getSecurityContext(headers);
			String contentType = getContentType(headers);
			byte[] content = in.readAllBytes();

			Binary resource = createBinary(contentType, content, securityContext);
			return delegate.update(id, resource, uri, headers);
		}
		catch (IOException e)
		{
			throw new WebApplicationException(e);
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy