001/* 002 * #%L 003 * HAPI FHIR JPA Server 004 * %% 005 * Copyright (C) 2014 - 2024 Smile CDR, Inc. 006 * %% 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 * #L% 019 */ 020package ca.uhn.fhir.jpa.util; 021 022import ca.uhn.fhir.i18n.Msg; 023import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; 024import ca.uhn.fhir.model.dstu2.resource.Subscription; 025import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum; 026import ca.uhn.fhir.model.dstu2.valueset.SubscriptionChannelTypeEnum; 027import ca.uhn.fhir.model.dstu2.valueset.SubscriptionStatusEnum; 028import ca.uhn.fhir.rest.api.RestOperationTypeEnum; 029import ca.uhn.fhir.rest.api.server.RequestDetails; 030import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; 031import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; 032import ca.uhn.fhir.rest.server.interceptor.ServerOperationInterceptorAdapter; 033import org.hl7.fhir.instance.model.api.IBaseResource; 034import org.springframework.beans.factory.annotation.Autowired; 035import org.springframework.beans.factory.annotation.Qualifier; 036 037import static org.apache.commons.lang3.StringUtils.isNotBlank; 038 039/** 040 * Interceptor which requires newly created {@link Subscription subscriptions} to be in 041 * {@link SubscriptionStatusEnum#REQUESTED} state and prevents clients from changing the status. 042 */ 043public class SubscriptionsRequireManualActivationInterceptorDstu2 extends ServerOperationInterceptorAdapter { 044 045 @Autowired 046 @Qualifier("mySubscriptionDaoDstu2") 047 private IFhirResourceDao<Subscription> myDao; 048 049 @Override 050 public void resourceCreated(RequestDetails theRequest, IBaseResource theResource) { 051 if (myDao.getContext().getResourceType(theResource).equals(ResourceTypeEnum.SUBSCRIPTION.getCode())) { 052 verifyStatusOk(RestOperationTypeEnum.CREATE, null, theResource); 053 } 054 } 055 056 @Override 057 public void resourceUpdated(RequestDetails theRequest, IBaseResource theOldResource, IBaseResource theNewResource) { 058 if (myDao.getContext().getResourceType(theNewResource).equals(ResourceTypeEnum.SUBSCRIPTION.getCode())) { 059 verifyStatusOk(RestOperationTypeEnum.UPDATE, theOldResource, theNewResource); 060 } 061 } 062 063 public void setDao(IFhirResourceDao<Subscription> theDao) { 064 myDao = theDao; 065 } 066 067 private void verifyStatusOk( 068 RestOperationTypeEnum theOperation, IBaseResource theOldResourceOrNull, IBaseResource theResource) { 069 Subscription subscription = (Subscription) theResource; 070 SubscriptionStatusEnum newStatus = subscription.getStatusElement().getValueAsEnum(); 071 072 if (newStatus == SubscriptionStatusEnum.REQUESTED || newStatus == SubscriptionStatusEnum.OFF) { 073 return; 074 } 075 076 if (newStatus == null) { 077 String actualCode = subscription.getStatusElement().getValueAsString(); 078 throw new UnprocessableEntityException(Msg.code(800) + "Can not " + theOperation.getCode() 079 + " resource: Subscription.status must be populated on this server" 080 + ((isNotBlank(actualCode)) ? " (invalid value " + actualCode + ")" : "")); 081 } 082 083 if (theOldResourceOrNull != null) { 084 try { 085 Subscription existing = (Subscription) theOldResourceOrNull; 086 SubscriptionStatusEnum existingStatus = 087 existing.getStatusElement().getValueAsEnum(); 088 if (existingStatus != newStatus) { 089 verifyActiveStatus(theOperation, subscription, newStatus, existingStatus); 090 } 091 } catch (ResourceNotFoundException e) { 092 verifyActiveStatus(theOperation, subscription, newStatus, null); 093 } 094 } else { 095 verifyActiveStatus(theOperation, subscription, newStatus, null); 096 } 097 } 098 099 private void verifyActiveStatus( 100 RestOperationTypeEnum theOperation, 101 Subscription theSubscription, 102 SubscriptionStatusEnum newStatus, 103 SubscriptionStatusEnum theExistingStatus) { 104 SubscriptionChannelTypeEnum channelType = 105 theSubscription.getChannel().getTypeElement().getValueAsEnum(); 106 107 if (channelType == null) { 108 throw new UnprocessableEntityException(Msg.code(801) + "Subscription.channel.type must be populated"); 109 } 110 111 if (channelType == SubscriptionChannelTypeEnum.WEBSOCKET) { 112 return; 113 } 114 115 if (theExistingStatus != null) { 116 throw new UnprocessableEntityException(Msg.code(802) + "Subscription.status can not be changed from " 117 + describeStatus(theExistingStatus) + " to " + describeStatus(newStatus)); 118 } 119 120 if (theSubscription.getStatus() == null) { 121 throw new UnprocessableEntityException( 122 Msg.code(803) + "Can not " + theOperation.getCode().toLowerCase() 123 + " resource: Subscription.status must be populated on this server"); 124 } 125 126 throw new UnprocessableEntityException( 127 Msg.code(804) + "Subscription.status must be '" + SubscriptionStatusEnum.OFF.getCode() + "' or '" 128 + SubscriptionStatusEnum.REQUESTED.getCode() + "' on a newly created subscription"); 129 } 130 131 private String describeStatus(SubscriptionStatusEnum existingStatus) { 132 String existingStatusString; 133 if (existingStatus != null) { 134 existingStatusString = '\'' + existingStatus.getCode() + '\''; 135 } else { 136 existingStatusString = "null"; 137 } 138 return existingStatusString; 139 } 140}