package ca.uhn.fhir.osgi.impl; /* * #%L * HAPI FHIR - OSGi Bundle * %% * Copyright (C) 2014 - 2017 University Health Network * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ca.uhn.fhir.osgi.FhirConfigurationException; import ca.uhn.fhir.osgi.FhirProviderBundle; import ca.uhn.fhir.osgi.FhirServer; /** * Manage the dynamic registration of FHIR Servers and FHIR Providers. * Methods on this Spring Bean will be invoked from OSGi Reference * Listeners when OSGi services are published for these interfaces. * * @author Akana, Inc. Professional Services * */ public class FhirServerManager { private static Logger log = LoggerFactory.getLogger(FhirServerManager.class); private static final String FIRST_SERVER = "#first"; private Map<String,FhirServer> registeredServers = new ConcurrentHashMap<String,FhirServer>(); private Map<String,Collection<Collection<Object>>> serverProviders = new ConcurrentHashMap<String,Collection<Collection<Object>>>(); private Collection<Collection<Object>> registeredProviders = Collections.synchronizedList(new ArrayList<Collection<Object>>()); private Map<String,Collection<Collection<Object>>> pendingProviders = new ConcurrentHashMap<String,Collection<Collection<Object>>>(); private boolean haveDefaultProviders = false; /** * Register a new FHIR Server OSGi service. * We need to track these services so we can find the correct * server to use when registering/unregistering providers. * <p> * The OSGi service definition of a FHIR Server should look like: * <code><pre> * <osgi:service ref="<b><i>some.bean</i></b>" interface="ca.uhn.fhir.osgi.FhirServer"> * <osgi:service-properties> * <entry key="name" value="<b><i>osgi-service-name</i></b>"/> * <entry key="fhir.server.name" value="<b><i>fhir-server-name</i></b>"/> * </osgi:service-properties> * </osgi:service> * </pre></code> * The <b><i>fhir-server-name</i></b> parameter is also specified for all * of the FHIR Providers that are to be dynamically registered with the * named FHIR Server. * * @param server OSGi service implementing the FhirService interface * @param props the <service-properties> for that service * * @throws FhirConfigurationException */ public void registerFhirServer (FhirServer server, Map<String,Object> props) throws FhirConfigurationException { if (server != null) { String serviceName = (String)props.get("name"); if (null == serviceName) { serviceName = "<default>"; } String serverName = (String)props.get(FhirServer.SVCPROP_SERVICE_NAME); if (serverName != null) { if (registeredServers.containsKey(serverName)) { throw new FhirConfigurationException("FHIR Server named ["+serverName+"] is already registered. These names must be unique."); } log.trace("Registering FHIR Server ["+serverName+"]. (OSGi service named ["+serviceName+"])"); registeredServers.put(serverName, server); if (haveDefaultProviders && registeredServers.size() > 1) { throw new FhirConfigurationException("FHIR Providers are registered without a server name. Only one FHIR Server is allowed."); } Collection<Collection<Object>> providers = pendingProviders.get(serverName); if (providers != null) { log.trace("Registering FHIR providers waiting for this server to be registered."); pendingProviders.remove(serverName); for (Collection<Object> list : providers) { this.registerProviders(list, server, serverName); } } if (registeredServers.size() == 1) { providers = pendingProviders.get(FIRST_SERVER); if (providers != null) { log.trace("Registering FHIR providers waiting for the first/only server to be registered."); pendingProviders.remove(FIRST_SERVER); for (Collection<Object> list : providers) { this.registerProviders(list, server, serverName); } } } } else { throw new FhirConfigurationException("FHIR Server registered in OSGi is missing the required ["+FhirServer.SVCPROP_SERVICE_NAME+"] service-property"); } } } /** * This method will be called when a FHIR Server OSGi service * is being removed from the container. This normally will only * occur when its bundle is stopped because it is being removed * or updated. * * @param server OSGi service implementing the FhirService interface * @param props the <service-properties> for that service * * @throws FhirConfigurationException */ public void unregisterFhirServer (FhirServer server, Map<String,Object> props) throws FhirConfigurationException { if (server != null) { String serverName = (String)props.get(FhirServer.SVCPROP_SERVICE_NAME); if (serverName != null) { FhirServer service = registeredServers.get(serverName); if (service != null) { log.trace("Unregistering FHIR Server ["+serverName+"]"); service.unregisterOsgiProviders(); registeredServers.remove(serverName); log.trace("Dequeue any FHIR providers waiting for this server"); pendingProviders.remove(serverName); if (registeredServers.size() == 0) { log.trace("Dequeue any FHIR providers waiting for the first/only server"); pendingProviders.remove(FIRST_SERVER); } Collection<Collection<Object>> providers = serverProviders.get(serverName); if (providers != null) { serverProviders.remove(serverName); registeredProviders.removeAll(providers); } } } else { throw new FhirConfigurationException("FHIR Server registered in OSGi is missing the required ["+FhirServer.SVCPROP_SERVICE_NAME+"] service-property"); } } } /** * Register a new FHIR Provider-Bundle OSGi service. * * This could be a "plain" provider that is published with the * FhirProvider interface or it could be a resource provider that * is published with either that same interface or the IResourceProvider * interface. * * (That check is not made here but is included as usage documentation) * * <p> * The OSGi service definition of a FHIR Provider would look like: * <code><pre> * <osgi:service ref="<b><i>some.bean</i></b>" interface="ca.uhn.fhir.osgi.IResourceProvider"> * <osgi:service-properties> * <entry key="name" value="<b><i>osgi-service-name</i></b>"/> * <entry key="fhir.server.name" value="<b><i>fhir-server-name</i></b>"/> * </osgi:service-properties> * </osgi:service> * </pre></code> * The <b><i>fhir-server-name</i></b> parameter is the value assigned to the * <code>fhir.server.name</code> service-property of one of the OSGi-published * FHIR Servers. * * @param server OSGi service implementing a FHIR provider interface * @param props the <service-properties> for that service * * @throws FhirConfigurationException */ public void registerFhirProviders (FhirProviderBundle bundle, Map<String,Object> props) throws FhirConfigurationException { if (bundle != null) { Collection<Object> providers = bundle.getProviders(); if (providers != null && !providers.isEmpty()) { try { String serverName = (String)props.get(FhirServer.SVCPROP_SERVICE_NAME); String ourServerName = getServerName(serverName); String bundleName = (String)props.get("name"); if (null == bundleName) { bundleName = "<default>"; } log.trace("Register FHIR Provider Bundle ["+bundleName+"] on FHIR Server ["+ourServerName+"]"); FhirServer server = registeredServers.get(ourServerName); if (server != null) { registerProviders(providers, server, serverName); } else { log.trace("Queue the Provider Bundle waiting for FHIR Server to be registered"); Collection<Collection<Object>> pending; synchronized(pendingProviders) { pending = pendingProviders.get(serverName); if (null == pending) { pending = Collections.synchronizedCollection(new ArrayList<Collection<Object>>()); pendingProviders.put(serverName, pending); } } pending.add(providers); } } catch (BadServerException e) { throw new FhirConfigurationException("Unable to register the OSGi FHIR Provider. Multiple Restful Servers exist. Specify the ["+FhirServer.SVCPROP_SERVICE_NAME+"] service-property"); } } } } protected void registerProviders (Collection<Object> providers, FhirServer server, String serverName) throws FhirConfigurationException { server.registerOsgiProviders(providers); Collection<Collection<Object>> active; synchronized(serverProviders) { active = serverProviders.get(serverName); if (null == active) { active = Collections.synchronizedCollection(new ArrayList<Collection<Object>>()); serverProviders.put(serverName, active); } } active.add(providers); registeredProviders.add(providers); } /** * This method will be called when a FHIR Provider OSGi service * is being removed from the container. This normally will only * occur when its bundle is stopped because it is being removed * or updated. * * @param server OSGi service implementing one of the provider * interfaces * @param props the <service-properties> for that service * * @throws FhirConfigurationException */ public void unregisterFhirProviders (FhirProviderBundle bundle, Map<String,Object> props) throws FhirConfigurationException { if (bundle != null) { Collection<Object> providers = bundle.getProviders(); if (providers != null && !providers.isEmpty()) { try { registeredProviders.remove(providers); String serverName = (String)props.get(FhirServer.SVCPROP_SERVICE_NAME); String ourServerName = getServerName(serverName); FhirServer server = registeredServers.get(ourServerName); if (server != null) { server.unregisterOsgiProviders(providers); Collection<Collection<Object>> active = serverProviders.get(serverName); if (active != null) { active.remove(providers); } } } catch (BadServerException e) { throw new FhirConfigurationException("Unable to register the OSGi FHIR Provider. Multiple Restful Servers exist. Specify the ["+FhirServer.SVCPROP_SERVICE_NAME+"] service-property"); } } } } /* * Adjust the FHIR Server name allowing for null which would * indicate that the Provider should be registered with the * only FHIR Server defined. */ private String getServerName (String osgiName) throws BadServerException { String result = osgiName; if (null == result) { if (registeredServers.isEmpty()) { // wait for the first one haveDefaultProviders = true; // only allow one server result = FIRST_SERVER; } else if (registeredServers.size() == 1) { // use the only one haveDefaultProviders = true; // only allow one server result = registeredServers.keySet().iterator().next(); } else { throw new BadServerException(); } } return result; } class BadServerException extends Exception { BadServerException() { super(); } } }