Java tutorial
/** * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under * the terms of the Healthcare Disclaimer located at http://openmrs.org/license. * * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS * graphic logo is a trademark of OpenMRS Inc. */ package org.openmrs.api.context; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.aopalliance.aop.Advice; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.openmrs.api.APIException; import org.openmrs.api.AdministrationService; import org.openmrs.api.CohortService; import org.openmrs.api.ConceptService; import org.openmrs.api.DatatypeService; import org.openmrs.api.EncounterService; import org.openmrs.api.FormService; import org.openmrs.api.LocationService; import org.openmrs.api.ObsService; import org.openmrs.api.OpenmrsService; import org.openmrs.api.OrderService; import org.openmrs.api.PatientService; import org.openmrs.api.PatientSetService; import org.openmrs.api.PersonService; import org.openmrs.api.ProgramWorkflowService; import org.openmrs.api.ProviderService; import org.openmrs.api.SerializationService; import org.openmrs.api.UserService; import org.openmrs.api.VisitService; import org.openmrs.arden.ArdenService; import org.openmrs.hl7.HL7Service; import org.openmrs.logic.LogicService; import org.openmrs.messagesource.MessageSourceService; import org.openmrs.notification.AlertService; import org.openmrs.notification.MessageService; import org.openmrs.notification.NoteService; import org.openmrs.scheduler.SchedulerService; import org.openmrs.util.OpenmrsClassLoader; import org.springframework.aop.Advisor; import org.springframework.aop.framework.Advised; import org.springframework.aop.framework.ProxyFactory; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; /** * Represents an OpenMRS <code>Service Context</code>, which returns the services represented * throughout the system. <br> * <br> * This class should not be access directly, but rather used through the <code>Context</code> class. <br> * <br> * This class is essentially static and only one instance is kept because this is fairly * heavy-weight. Spring takes care of filling in the actual service implementations via dependency * injection. See the /metadata/api/spring/applicationContext-service.xml file. <br> * <br> * Module services are also accessed through this class. See {@link #getService(Class)} * * @see org.openmrs.api.context.Context */ public class ServiceContext implements ApplicationContextAware { private static final Log log = LogFactory.getLog(ServiceContext.class); private ApplicationContext applicationContext; private static boolean refreshingContext = false; private static final Object refreshingContextLock = new Object(); /** * Static variable holding whether or not to use the system classloader. By default this is * false so the openmrs classloader is used instead */ private boolean useSystemClassLoader = false; // Cached service objects @SuppressWarnings("unchecked") Map<Class, Object> services = new HashMap<Class, Object>(); // Advisors added to services by this service @SuppressWarnings("unchecked") Map<Class, Set<Advisor>> addedAdvisors = new HashMap<Class, Set<Advisor>>(); // Advice added to services by this service @SuppressWarnings("unchecked") Map<Class, Set<Advice>> addedAdvice = new HashMap<Class, Set<Advice>>(); /** * Services implementing the OpenmrsService interface for each module. The map is keyed by the * full class name including package. * * @since 1.9 */ @SuppressWarnings("unchecked") Map<String, OpenmrsService> moduleOpenmrsServices = new HashMap<String, OpenmrsService>(); /** * The default constructor is private so as to keep only one instance per java vm. * * @see ServiceContext#getInstance() */ private ServiceContext() { log.debug("Instantiating service context"); } private static class ServiceContextHolder { private static ServiceContext instance = null; } /** * There should only be one ServiceContext per openmrs (java virtual machine). This method * should be used when wanting to fetch the service context Note: The ServiceContext shouldn't * be used independently. All calls should go through the Context * * @return This VM's current ServiceContext. * @see org.openmrs.api.context.Context */ public static ServiceContext getInstance() { if (ServiceContextHolder.instance == null) { ServiceContextHolder.instance = new ServiceContext(); } return ServiceContextHolder.instance; } /** * Null out the current instance of the ServiceContext. This should be used when modules are * refreshing (being added/removed) and/or openmrs is shutting down */ public static void destroyInstance() { if (ServiceContextHolder.instance != null && ServiceContextHolder.instance.services != null) { if (log.isDebugEnabled()) { for (Map.Entry<Class, Object> entry : ServiceContextHolder.instance.services.entrySet()) { log.debug("Service - " + entry.getKey().getName() + ":" + entry.getValue()); } } // Remove advice and advisors that this service added for (Class serviceClass : ServiceContextHolder.instance.services.keySet()) { ServiceContextHolder.instance.removeAddedAOP(serviceClass); } if (ServiceContextHolder.instance.services != null) { ServiceContextHolder.instance.services.clear(); ServiceContextHolder.instance.services = null; } if (ServiceContextHolder.instance.addedAdvisors != null) { ServiceContextHolder.instance.addedAdvisors.clear(); ServiceContextHolder.instance.addedAdvisors = null; } if (ServiceContextHolder.instance.addedAdvice != null) { ServiceContextHolder.instance.addedAdvice.clear(); ServiceContextHolder.instance.addedAdvice = null; } } if (ServiceContextHolder.instance != null) { ServiceContextHolder.instance.applicationContext = null; if (ServiceContextHolder.instance.moduleOpenmrsServices != null) { ServiceContextHolder.instance.moduleOpenmrsServices.clear(); ServiceContextHolder.instance.moduleOpenmrsServices = null; } } if (log.isDebugEnabled()) { log.debug("Destroying ServiceContext instance: " + ServiceContextHolder.instance); } ServiceContextHolder.instance = null; } /** * @return encounter-related services */ public EncounterService getEncounterService() { return getService(EncounterService.class); } /** * @return location services */ public LocationService getLocationService() { return getService(LocationService.class); } /** * @return observation services */ public ObsService getObsService() { return getService(ObsService.class); } /** * @return note service */ public NoteService getNoteService() { return getService(NoteService.class); } /** * @return patientset-related services */ public PatientSetService getPatientSetService() { return getService(PatientSetService.class); } /** * @return cohort related service */ public CohortService getCohortService() { return getService(CohortService.class); } /** * @param cs cohort related service */ public void setCohortService(CohortService cs) { setService(CohortService.class, cs); } /** * @return order service */ public OrderService getOrderService() { return getService(OrderService.class); } /** * @return form service */ public FormService getFormService() { return getService(FormService.class); } /** * @return serialization service */ public SerializationService getSerializationService() { return getService(SerializationService.class); } /** * @return admin-related services */ public AdministrationService getAdministrationService() { return getService(AdministrationService.class); } /** * @return programWorkflowService */ public ProgramWorkflowService getProgramWorkflowService() { return getService(ProgramWorkflowService.class); } /** * @return ardenService */ public ArdenService getArdenService() { return getService(ArdenService.class); } /** * @return logicService */ public LogicService getLogicService() { return getService(LogicService.class); } /** * @return scheduler service */ public SchedulerService getSchedulerService() { return getService(SchedulerService.class); } /** * Set the scheduler service. * * @param schedulerService */ public void setSchedulerService(SchedulerService schedulerService) { setService(SchedulerService.class, schedulerService); } /** * @return alert service */ public AlertService getAlertService() { return getService(AlertService.class); } /** * @param alertService */ public void setAlertService(AlertService alertService) { setService(AlertService.class, alertService); } /** * @param programWorkflowService */ public void setProgramWorkflowService(ProgramWorkflowService programWorkflowService) { setService(ProgramWorkflowService.class, programWorkflowService); } /** * @param ardenService */ public void setArdenService(ArdenService ardenService) { setService(ArdenService.class, ardenService); } /** * @param logicService */ public void setLogicService(LogicService logicService) { setService(LogicService.class, logicService); } /** * @return message service */ public MessageService getMessageService() { return getService(MessageService.class); } /** * Sets the message service. * * @param messageService */ public void setMessageService(MessageService messageService) { setService(MessageService.class, messageService); } /** * @return the hl7Service */ public HL7Service getHL7Service() { return getService(HL7Service.class); } /** * @param hl7Service the hl7Service to set */ public void setHl7Service(HL7Service hl7Service) { setService(HL7Service.class, hl7Service); } /** * @param administrationService the administrationService to set */ public void setAdministrationService(AdministrationService administrationService) { setService(AdministrationService.class, administrationService); } /** * @param encounterService the encounterService to set */ public void setEncounterService(EncounterService encounterService) { setService(EncounterService.class, encounterService); } /** * @param locationService the LocationService to set */ public void setLocationService(LocationService locationService) { setService(LocationService.class, locationService); } /** * @param formService the formService to set */ public void setFormService(FormService formService) { setService(FormService.class, formService); } /** * @param obsService the obsService to set */ public void setObsService(ObsService obsService) { setService(ObsService.class, obsService); } /** * @param noteService the noteService to set */ public void setNoteService(NoteService noteService) { setService(NoteService.class, noteService); } /** * @param orderService the orderService to set */ public void setOrderService(OrderService orderService) { setService(OrderService.class, orderService); } /** * @param patientSetService the patientSetService to set */ public void setPatientSetService(PatientSetService patientSetService) { setService(PatientSetService.class, patientSetService); } /** * @param serializationService */ public void setSerializationService(SerializationService serializationService) { setService(SerializationService.class, serializationService); } /** * @return patient related services */ public PatientService getPatientService() { return getService(PatientService.class); } /** * @param patientService the patientService to set */ public void setPatientService(PatientService patientService) { setService(PatientService.class, patientService); } /** * @return person related services */ public PersonService getPersonService() { return getService(PersonService.class); } /** * @param personService the personService to set */ public void setPersonService(PersonService personService) { setService(PersonService.class, personService); } /** * @return concept related services */ public ConceptService getConceptService() { return getService(ConceptService.class); } /** * @param conceptService the conceptService to set */ public void setConceptService(ConceptService conceptService) { setService(ConceptService.class, conceptService); } /** * @return user-related services */ public UserService getUserService() { return getService(UserService.class); } /** * @param userService the userService to set */ public void setUserService(UserService userService) { setService(UserService.class, userService); } /** * Gets the MessageSourceService used in the context. * * @return MessageSourceService */ public MessageSourceService getMessageSourceService() { return getService(MessageSourceService.class); } /** * Sets the MessageSourceService used in the context. * * @param messageSourceService the MessageSourceService to use */ public void setMessageSourceService(MessageSourceService messageSourceService) { setService(MessageSourceService.class, messageSourceService); } /** * @param cls * @param advisor */ @SuppressWarnings("unchecked") public void addAdvisor(Class cls, Advisor advisor) { Advised advisedService = (Advised) services.get(cls); if (advisedService.indexOf(advisor) < 0) { advisedService.addAdvisor(advisor); } if (addedAdvisors.get(cls) == null) { addedAdvisors.put(cls, new HashSet<Advisor>()); } getAddedAdvisors(cls).add(advisor); } /** * @param cls * @param advice */ @SuppressWarnings("unchecked") public void addAdvice(Class cls, Advice advice) { Advised advisedService = (Advised) services.get(cls); if (advisedService.indexOf(advice) < 0) { advisedService.addAdvice(advice); } if (addedAdvice.get(cls) == null) { addedAdvice.put(cls, new HashSet<Advice>()); } getAddedAdvice(cls).add(advice); } /** * @param cls * @param advisor */ @SuppressWarnings("unchecked") public void removeAdvisor(Class cls, Advisor advisor) { Advised advisedService = (Advised) services.get(cls); advisedService.removeAdvisor(advisor); getAddedAdvisors(cls).remove(advisor); } /** * @param cls * @param advice */ @SuppressWarnings("unchecked") public void removeAdvice(Class cls, Advice advice) { Advised advisedService = (Advised) services.get(cls); advisedService.removeAdvice(advice); getAddedAdvice(cls).remove(advice); } /** * Moves advisors and advice added by ServiceContext from the source service to the target one. * * @param source the existing service * @param target the new service */ @SuppressWarnings("unchecked") private void moveAddedAOP(Advised source, Advised target) { Class serviceClass = source.getClass(); Set<Advisor> existingAdvisors = getAddedAdvisors(serviceClass); for (Advisor advisor : existingAdvisors) { target.addAdvisor(advisor); source.removeAdvisor(advisor); } Set<Advice> existingAdvice = getAddedAdvice(serviceClass); for (Advice advice : existingAdvice) { target.addAdvice(advice); source.removeAdvice(advice); } } /** * Removes all advice and advisors added by ServiceContext. * * @param cls the class of the cached service to cleanup */ @SuppressWarnings("unchecked") private void removeAddedAOP(Class cls) { removeAddedAdvisors(cls); removeAddedAdvice(cls); } /** * Removes all the advisors added by ServiceContext. * * @param cls the class of the cached service to cleanup */ @SuppressWarnings("unchecked") private void removeAddedAdvisors(Class cls) { Advised advisedService = (Advised) services.get(cls); Set<Advisor> advisorsToRemove = addedAdvisors.get(cls); if (advisedService != null && advisorsToRemove != null) { for (Advisor advisor : advisorsToRemove.toArray(new Advisor[] {})) { removeAdvisor(cls, advisor); } } } /** * Returns the set of advisors added by ServiceContext. * * @param cls the class of the cached service * @return the set of advisors or an empty set */ @SuppressWarnings("unchecked") private Set<Advisor> getAddedAdvisors(Class cls) { Set<Advisor> result = addedAdvisors.get(cls); return (Set<Advisor>) (result == null ? Collections.emptySet() : result); } /** * Removes all the advice added by the ServiceContext. * * @param cls the class of the caches service to cleanup */ @SuppressWarnings("unchecked") private void removeAddedAdvice(Class cls) { Advised advisedService = (Advised) services.get(cls); Set<Advice> adviceToRemove = addedAdvice.get(cls); if (advisedService != null && adviceToRemove != null) { for (Advice advice : adviceToRemove.toArray(new Advice[] {})) { removeAdvice(cls, advice); } } } /** * Returns the set of advice added by ServiceContext. * * @param cls the class of the cached service * @return the set of advice or an empty set */ @SuppressWarnings("unchecked") private Set<Advice> getAddedAdvice(Class cls) { Set<Advice> result = addedAdvice.get(cls); return (Set<Advice>) (result == null ? Collections.emptySet() : result); } /** * Returns the current proxy that is stored for the Class <code>cls</code> * * @param cls * @return Object that is a proxy for the <code>cls</code> class */ @SuppressWarnings("unchecked") public <T extends Object> T getService(Class<? extends T> cls) { if (log.isTraceEnabled()) { log.trace("Getting service: " + cls); } // if the context is refreshing, wait until it is // done -- otherwise a null service might be returned synchronized (refreshingContextLock) { try { while (refreshingContext) { if (log.isDebugEnabled()) { log.debug("Waiting to get service: " + cls + " while the context is being refreshed"); } refreshingContextLock.wait(); if (log.isDebugEnabled()) { log.debug("Finished waiting to get service " + cls + " while the context was being refreshed"); } } } catch (InterruptedException e) { log.warn("Refresh lock was interrupted", e); } } Object service = services.get(cls); if (service == null) { throw new APIException("Service not found: " + cls); } return (T) service; } /** * Allow other services to be added to our service layer * * @param cls Interface to proxy * @param classInstance the actual instance of the <code>cls</code> interface */ @SuppressWarnings("unchecked") public void setService(Class cls, Object classInstance) { log.debug("Setting service: " + cls); if (cls != null && classInstance != null) { try { Advised cachedService = (Advised) services.get(cls); boolean noExistingService = cachedService == null; boolean replacingService = cachedService != null && cachedService != classInstance; boolean serviceAdvised = classInstance instanceof Advised; if (noExistingService || replacingService) { Advised advisedService; if (!serviceAdvised) { // Adding a bare service, wrap with AOP proxy Class[] interfaces = { cls }; ProxyFactory factory = new ProxyFactory(interfaces); factory.setTarget(classInstance); advisedService = (Advised) factory.getProxy(OpenmrsClassLoader.getInstance()); } else { advisedService = (Advised) classInstance; } if (replacingService) { moveAddedAOP(cachedService, advisedService); } services.put(cls, advisedService); } log.debug("Service: " + cls + " set successfully"); } catch (Exception e) { throw new APIException("service.unable.create.proxy.factory", new Object[] { classInstance.getClass().getName() }, e); } } } /** * Allow other services to be added to our service layer <br> * <br> * Classes will be found/loaded with the ModuleClassLoader <br> * <br> * <code>params</code>[0] = string representing the service interface<br> * <code>params</code>[1] = service instance * * @param params list of parameters */ @SuppressWarnings("unchecked") public void setModuleService(List<Object> params) { String classString = (String) params.get(0); Object classInstance = params.get(1); if (classString == null || classInstance == null) { throw new APIException("service.unable.find", (Object[]) null); } Class cls = null; // load the given 'classString' class from either the openmrs class // loader or the system class loader depending on if we're in a testing // environment or not (system == testing, openmrs == normal) try { if (!useSystemClassLoader) { cls = OpenmrsClassLoader.getInstance().loadClass(classString); if (cls != null && log.isDebugEnabled()) { try { log.debug("cls classloader: " + cls.getClass().getClassLoader() + " uid: " + cls.getClass().getClassLoader().hashCode()); } catch (Exception e) { /*pass*/} } } else if (useSystemClassLoader) { try { cls = Class.forName(classString); if (log.isDebugEnabled()) { log.debug("cls2 classloader: " + cls.getClass().getClassLoader() + " uid: " + cls.getClass().getClassLoader().hashCode()); //pay attention that here, cls = Class.forName(classString), the system class loader and //cls2 is the openmrs class loader, like above. log.debug("cls==cls2: " + String.valueOf(cls == OpenmrsClassLoader.getInstance().loadClass(classString))); } } catch (Exception e) { /*pass*/} } } catch (ClassNotFoundException e) { throw new APIException("service.unable.set", new Object[] { classString }, e); } // add this module service to the normal list of services setService(cls, classInstance); //Run onStartup for all services implementing the OpenmrsService interface. if (OpenmrsService.class.isAssignableFrom(classInstance.getClass())) { moduleOpenmrsServices.put(classString, (OpenmrsService) classInstance); runOpenmrsServiceOnStartup((OpenmrsService) classInstance, classString); } } /** * Set this service context to use the system class loader if the * <code>useSystemClassLoader</code> is set to true. If false, the openmrs class loader is used * to load module services * * @param useSystemClassLoader true/false whether to use the system class loader */ public void setUseSystemClassLoader(boolean useSystemClassLoader) { this.useSystemClassLoader = useSystemClassLoader; } /** * Checks if we are using the system class loader. * * @return true if using the system class loader, else false. */ public boolean isUseSystemClassLoader() { return useSystemClassLoader; } public static void setRefreshingContext(boolean refreshingContext) { ServiceContext.refreshingContext = refreshingContext; } /** * Should be called <b>right before</b> any spring context refresh This forces all calls to * getService to wait until <code>doneRefreshingContext</code> is called */ public void startRefreshingContext() { synchronized (refreshingContextLock) { log.info("Refreshing Context"); setRefreshingContext(true); } } /** * Should be called <b>right after</b> any spring context refresh This wakes up all calls to * getService that were waiting because <code>startRefreshingContext</code> was called */ public void doneRefreshingContext() { synchronized (refreshingContextLock) { log.info("Done refreshing Context"); setRefreshingContext(false); refreshingContextLock.notifyAll(); } } /** * Returns true/false whether startRefreshingContext() has been called without a subsequent call * to doneRefreshingContext() yet. All methods involved in starting/stopping a module should * call this if a service method is needed -- otherwise a deadlock will occur. * * @return true/false whether the services are currently blocking waiting for a call to * doneRefreshingContext() */ public boolean isRefreshingContext() { synchronized (refreshingContextLock) { return refreshingContext; } } /** * Retrieves all Beans which have been registered in the Spring {@link ApplicationContext} that * match the given object type (including subclasses). * <p> * <b>NOTE: This method introspects top-level beans only.</b> It does <i>not</i> check nested * beans which might match the specified type as well. * * @see ApplicationContext#getBeansOfType(Class) * @param type the type of Bean to retrieve from the Spring {@link ApplicationContext} * @return a List of all registered Beans that are valid instances of the passed type * @since 1.5 * @should return a list of all registered beans of the passed type * @should return beans registered in a module * @should return an empty list if no beans have been registered of the passed type */ public <T> List<T> getRegisteredComponents(Class<T> type) { Map<String, T> m = getRegisteredComponents(applicationContext, type); if (log.isTraceEnabled()) { log.trace("getRegisteredComponents(" + type + ") = " + m); } return new ArrayList<T>(m.values()); } /** * Retrieves a bean that match the given type (including subclasses) and name. * * @param beanName the name of registered bean to retrieve * @param type the type of bean to retrieve * @return bean of passed type * * @since 1.9.4 */ public <T> T getRegisteredComponent(String beanName, Class<T> type) throws APIException { try { return applicationContext.getBean(beanName, type); } catch (BeansException beanException) { throw new APIException("service.error.during.getting.component", null, beanException); } } /** * Private method which returns all components registered in a Spring applicationContext of a * given type This method recurses through each parent ApplicationContext * * @param context - The applicationContext to check * @param type - The type of component to retrieve * @return all components registered in a Spring applicationContext of a given type */ @SuppressWarnings("unchecked") private <T> Map<String, T> getRegisteredComponents(ApplicationContext context, Class<T> type) { Map<String, T> components = new HashMap<String, T>(); Map registeredComponents = context.getBeansOfType(type); if (log.isTraceEnabled()) { log.trace("getRegisteredComponents(" + context + ", " + type + ") = " + registeredComponents); } if (registeredComponents != null) { components.putAll(registeredComponents); } if (context.getParent() != null) { components.putAll(getRegisteredComponents(context.getParent(), type)); } return components; } /** * @param applicationContext the applicationContext to set */ public void setApplicationContext(ApplicationContext applicationContext) { this.applicationContext = applicationContext; } public ApplicationContext getApplicationContext() { return applicationContext; } /** * Calls the {@link OpenmrsService#onStartup()} method for an instance implementing the * {@link OpenmrsService} interface. * * @param openmrsService instance implementing the {@link OpenmrsService} interface. * @param classString the full instance class name including the package name. * @since 1.9 */ private void runOpenmrsServiceOnStartup(final OpenmrsService openmrsService, final String classString) { new Thread() { @Override public void run() { try { synchronized (refreshingContextLock) { //Need to wait for application context to finish refreshing otherwise we get into trouble. while (refreshingContext) { if (log.isDebugEnabled()) { log.debug("Waiting to get service: " + classString + " while the context" + " is being refreshed"); } refreshingContextLock.wait(); if (log.isDebugEnabled()) { log.debug("Finished waiting to get service " + classString + " while the context was being refreshed"); } } } Daemon.runStartupForService(openmrsService); } catch (InterruptedException e) { log.warn("Refresh lock was interrupted while waiting to run OpenmrsService.onStartup() for " + classString, e); } } }.start(); } /** * Gets a list of services implementing the {@link OpenmrsService} interface, for a given * module. * * @param modulePackage the module's package name. * @return the list of service instances. * @since 1.9 */ public List<OpenmrsService> getModuleOpenmrsServices(String modulePackage) { List<OpenmrsService> services = new ArrayList<OpenmrsService>(); for (Entry<String, OpenmrsService> entry : moduleOpenmrsServices.entrySet()) { if (entry.getKey().startsWith(modulePackage)) { services.add(entry.getValue()); } } return services; } /** * Gets the visit service. * * @return visit service. * @since 1.9 **/ public VisitService getVisitService() { return getService(VisitService.class); } /** * Sets the visit service. * * @param visitService the visitService to set * @since 1.9 **/ public void setVisitService(VisitService visitService) { setService(VisitService.class, visitService); } /** * Gets the provider service. * * @return provider service. * @since 1.9 **/ public ProviderService getProviderService() { return getService(ProviderService.class); } /** * Sets the provider service. * * @param providerService the providerService to set * @since 1.9 **/ public void setProviderService(ProviderService providerService) { setService(ProviderService.class, providerService); } /** * Gets the datatype service * * @return custom datatype service * @since 1.9 */ public DatatypeService getDatatypeService() { return getService(DatatypeService.class); } /** * Sets the datatype service * * @param datatypeService the datatypeService to set * @since 1.9 */ public void setDatatypeService(DatatypeService datatypeService) { setService(DatatypeService.class, datatypeService); } }