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.valueset.ResourceTypeEnum; 025import ca.uhn.fhir.rest.api.RestOperationTypeEnum; 026import ca.uhn.fhir.rest.api.server.RequestDetails; 027import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; 028import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; 029import ca.uhn.fhir.rest.server.interceptor.ServerOperationInterceptorAdapter; 030import org.hl7.fhir.dstu3.model.Subscription; 031import org.hl7.fhir.dstu3.model.Subscription.SubscriptionChannelType; 032import org.hl7.fhir.dstu3.model.Subscription.SubscriptionStatus; 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 SubscriptionStatus#REQUESTED} state and prevents clients from changing the status. 042 */ 043public class SubscriptionsRequireManualActivationInterceptorDstu3 extends ServerOperationInterceptorAdapter { 044 045 @Autowired 046 @Qualifier("mySubscriptionDaoDstu3") 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 SubscriptionStatus newStatus = subscription.getStatusElement().getValue(); 071 072 if (newStatus == SubscriptionStatus.REQUESTED || newStatus == SubscriptionStatus.OFF) { 073 return; 074 } 075 076 if (newStatus == null) { 077 String actualCode = subscription.getStatusElement().getValueAsString(); 078 throw new UnprocessableEntityException(Msg.code(812) + "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 SubscriptionStatus existingStatus = existing.getStatusElement().getValue(); 087 if (existingStatus != newStatus) { 088 verifyActiveStatus(theOperation, subscription, newStatus, existingStatus); 089 } 090 } catch (ResourceNotFoundException e) { 091 verifyActiveStatus(theOperation, subscription, newStatus, null); 092 } 093 } else { 094 verifyActiveStatus(theOperation, subscription, newStatus, null); 095 } 096 } 097 098 private void verifyActiveStatus( 099 RestOperationTypeEnum theOperation, 100 Subscription theSubscription, 101 SubscriptionStatus newStatus, 102 SubscriptionStatus theExistingStatus) { 103 SubscriptionChannelType channelType = 104 theSubscription.getChannel().getTypeElement().getValue(); 105 106 if (channelType == null) { 107 throw new UnprocessableEntityException(Msg.code(813) + "Subscription.channel.type must be populated"); 108 } 109 110 if (channelType == SubscriptionChannelType.WEBSOCKET) { 111 return; 112 } 113 114 if (theExistingStatus != null) { 115 throw new UnprocessableEntityException(Msg.code(814) + "Subscription.status can not be changed from " 116 + describeStatus(theExistingStatus) + " to " + describeStatus(newStatus)); 117 } 118 119 if (theSubscription.getStatus() == null) { 120 throw new UnprocessableEntityException( 121 Msg.code(815) + "Can not " + theOperation.getCode().toLowerCase() 122 + " resource: Subscription.status must be populated on this server"); 123 } 124 125 throw new UnprocessableEntityException( 126 Msg.code(816) + "Subscription.status must be '" + SubscriptionStatus.OFF.toCode() + "' or '" 127 + SubscriptionStatus.REQUESTED.toCode() + "' on a newly created subscription"); 128 } 129 130 private String describeStatus(SubscriptionStatus existingStatus) { 131 String existingStatusString; 132 if (existingStatus != null) { 133 existingStatusString = '\'' + existingStatus.toCode() + '\''; 134 } else { 135 existingStatusString = "null"; 136 } 137 return existingStatusString; 138 } 139}