001/*- 002 * #%L 003 * HAPI FHIR - Server Framework 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.rest.server.provider; 021 022import jakarta.annotation.Nonnull; 023import org.slf4j.Logger; 024import org.slf4j.LoggerFactory; 025 026import java.util.ArrayList; 027import java.util.Collections; 028import java.util.HashSet; 029import java.util.LinkedHashSet; 030import java.util.List; 031import java.util.Set; 032import java.util.function.Supplier; 033 034/** 035 * This is a generic implementation of the <a href="https://refactoring.guru/design-patterns/observer">Observer Design Pattern</a>. 036 * We use this to pass sets of beans from exporting Spring application contexts to importing Spring application contexts. We defer 037 * resolving the observed beans via a Supplier to give the exporting context a chance to initialize the beans before they are used. 038 * @param <T> the class of the Observer 039 * <p> 040 * A typical usage pattern would be: 041 * <ol> 042 * <li>Create {@link ObservableSupplierSet} in exporter context.</li> 043 * <li>Add all the suppliers in the exporter context.</li> 044 * <li>Attach the importer to the {@link ObservableSupplierSet}</li> 045 * <li>Importer calls {@link ObservableSupplierSet#getSupplierResults} and processes all the beans</li> 046 * <li>Some other service beans may add more suppliers later as a part of their initialization and the observer handlers will process them accordingly</li> 047 * <li>Those other service beans should call {@link ObservableSupplierSet#removeSupplier(Supplier)} in a @PreDestroy method so they are properly cleaned up if those services are shut down or restarted</li> 048 * </ol> 049 * 050 */ 051public class ObservableSupplierSet<T extends IObservableSupplierSetObserver> { 052 private static final Logger ourLog = LoggerFactory.getLogger(ObservableSupplierSet.class); 053 054 private final Set<T> myObservers = Collections.synchronizedSet(new HashSet<>()); 055 056 private final Set<Supplier<Object>> mySuppliers = new LinkedHashSet<>(); 057 058 /** Add a supplier and notify all observers 059 * 060 * @param theSupplier supplies the object to be observed 061 */ 062 public void addSupplier(@Nonnull Supplier<Object> theSupplier) { 063 if (mySuppliers.add(theSupplier)) { 064 myObservers.forEach(observer -> observer.update(theSupplier)); 065 } 066 } 067 068 /** Remove a supplier and notify all observers. CAUTION, you might think that this code would work, but it does not: 069 * <code> 070 * observableSupplierSet.addSupplier(() -> myBean); 071 * ... 072 * observableSupplierSet.removeSupplier(() -> myBean); 073 * </code> 074 * the removeSupplier in this example would fail because it is a different lambda instance from the first. Instead, 075 * you need to store the supplier between the add and remove: 076 * <code> 077 * mySupplier = () -> myBean; 078 * observableSupplierSet.addSupplier(mySupplier); 079 * ... 080 * observableSupplierSet.removeSupplier(mySupplier); 081 * </code> 082 * 083 * @param theSupplier the supplier to be removed 084 */ 085 public void removeSupplier(@Nonnull Supplier<Object> theSupplier) { 086 if (mySuppliers.remove(theSupplier)) { 087 myObservers.forEach(observer -> observer.remove(theSupplier)); 088 } else { 089 ourLog.warn("Failed to remove supplier", new RuntimeException()); 090 } 091 } 092 093 /** 094 * Attach an observer to this observableSupplierSet. This observer will be notified every time a supplier is added or removed. 095 * @param theObserver the observer to be notified 096 */ 097 public void attach(T theObserver) { 098 myObservers.add(theObserver); 099 } 100 101 /** 102 * Detach an observer from this observableSupplierSet, so it is no longer notified when suppliers are added and removed. 103 * @param theObserver the observer to be removed 104 */ 105 public void detach(T theObserver) { 106 myObservers.remove(theObserver); 107 } 108 109 /** 110 * 111 * @return a list of get() being called on all suppliers. 112 */ 113 protected List<Object> getSupplierResults() { 114 List<Object> retVal = new ArrayList<>(); 115 for (Supplier<Object> next : mySuppliers) { 116 Object nextRp = next.get(); 117 if (nextRp != null) { 118 retVal.add(nextRp); 119 } 120 } 121 return retVal; 122 } 123}