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;
021
022import ca.uhn.fhir.model.api.annotation.ResourceDef;
023import org.hl7.fhir.instance.model.api.IBaseResource;
024
025import java.util.Collections;
026import java.util.LinkedList;
027import java.util.List;
028import java.util.Optional;
029
030/**
031 * <pre>
032 * When populating the StructureDefinition links in a capability statement,
033 * it can be useful to know the lowest common superclass for the profiles in use for a given resource name.
034 * This class finds this superclass, by incrementally computing the greatest common sequence of ancestor classes in the class hierarchies of registered resources.
035 * For instance, given the following classes
036 * MyPatient extends Patient
037 * MyPatient2 extends MyPatient
038 * MyPatient3 extends MyPatient
039 * MyPatient4 extends MyPatient3
040 * this class will find the common ancestor sequence "IBaseResource -> Patient -> MyPatient". MyPatient is the lowest common superclass in this hierarchy.
041 * </pre>
042 *
043 */
044public class CommonResourceSupertypeScanner {
045
046        private List<Class<? extends IBaseResource>> greatestSharedAncestorsDescending;
047        private boolean initialized;
048
049        /**
050         * Recomputes the lowest common superclass by adding a new resource definition to the hierarchy.
051         * @param resourceClass  The resource class to add.
052         */
053        public void register(Class<? extends IBaseResource> resourceClass) {
054                List<Class<? extends IBaseResource>> resourceClassesInHierarchy = new LinkedList<>();
055                Class<?> currentClass = resourceClass;
056                while (IBaseResource.class.isAssignableFrom(currentClass)
057                                && currentClass.getAnnotation(ResourceDef.class) != null) {
058                        resourceClassesInHierarchy.add((Class<? extends IBaseResource>) currentClass);
059                        currentClass = currentClass.getSuperclass();
060                }
061                Collections.reverse(resourceClassesInHierarchy);
062                if (initialized) {
063                        for (int i = 0;
064                                        i < Math.min(resourceClassesInHierarchy.size(), greatestSharedAncestorsDescending.size());
065                                        i++) {
066                                if (greatestSharedAncestorsDescending.get(i) != resourceClassesInHierarchy.get(i)) {
067                                        greatestSharedAncestorsDescending = greatestSharedAncestorsDescending.subList(0, i);
068                                        break;
069                                }
070                        }
071                } else {
072                        greatestSharedAncestorsDescending = resourceClassesInHierarchy;
073                        initialized = true;
074                }
075        }
076
077        /**
078         * @return The lowest common superclass of currently registered resources.
079         */
080        public Optional<Class<? extends IBaseResource>> getLowestCommonSuperclass() {
081                if (!initialized || greatestSharedAncestorsDescending.isEmpty()) {
082                        return Optional.empty();
083                }
084                return Optional.ofNullable(greatestSharedAncestorsDescending.get(greatestSharedAncestorsDescending.size() - 1));
085        }
086}