package org.jtheque.modules.impl; /* * Copyright JTheque (Baptiste Wicht) * * 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. */ import org.jtheque.core.Core; import org.jtheque.core.Folders; import org.jtheque.images.ImageService; import org.jtheque.modules.Module; import org.jtheque.modules.ModuleDescription; import org.jtheque.modules.ModuleException; import org.jtheque.modules.ModuleListener; import org.jtheque.modules.ModuleResourceCache; import org.jtheque.modules.ModuleService; import org.jtheque.modules.ModuleState; import org.jtheque.modules.Repository; import org.jtheque.modules.SwingLoader; import org.jtheque.states.StateService; import org.jtheque.utils.SimplePropertiesCache; import org.jtheque.utils.annotations.GuardedInternally; import org.jtheque.utils.collections.CollectionUtils; import org.jtheque.utils.collections.WeakEventListenerList; import org.jtheque.utils.ui.SwingUtils; import org.slf4j.LoggerFactory; import org.springframework.core.io.UrlResource; import javax.annotation.Resource; import java.io.File; import java.util.Collection; import java.util.Map; import java.util.concurrent.ConcurrentMap; import static org.jtheque.modules.ModuleState.*; /** * A module manager implementation. It manage the cycle life of the modules. * * @author Baptiste Wicht */ public final class ModuleServiceImpl implements ModuleService, ModuleLauncher { private final Map<String, SwingLoader> loaders = CollectionUtils.newConcurrentMap(5); private final Map<Module, ModuleResources> resources = CollectionUtils.newConcurrentMap(5); @GuardedInternally private final WeakEventListenerList<ModuleListener> listeners = WeakEventListenerList.create(); /** * The configuration of the module manager. It seems the informations about the modules who're installed or * disabled. */ @GuardedInternally private final ModuleConfiguration configuration; @Resource private Core core; @Resource private ImageService imageService; @Resource private ModuleManager moduleManager; private volatile boolean loaded; private volatile boolean started; /** * Indicate if there is a collection module. */ private boolean collectionModule; private final ConcurrentMap<String, Object> moduleLocks = CollectionUtils.newConcurrentMap(10); /** * Create a new ModuleServiceImpl. * * @param stateService The state service. */ public ModuleServiceImpl(StateService stateService) { super(); configuration = stateService.getState(new ModuleConfiguration()); } @Override public void load() { SwingUtils.assertNotEDT("load()"); if (loaded) { throw new IllegalStateException("Cannot be loaded twice"); } loaded = true; moduleManager.loadModules(); for (Module module : moduleManager.getModules()) { configuration.setInitialState(module); //If a collection module must be launched if (ModuleManager.canBeLoaded(module) && module.isCollection()) { collectionModule = true; } //Indicate the module as installed fireModuleInstalled(module); } } /** * Plug the modules. */ @Override public void startModules() { SwingUtils.assertNotEDT("startModules()"); if (started) { throw new IllegalStateException("Cannot be started twice"); } started = true; moduleManager.startAll(this); } @Override public Collection<Module> getModules() { return CollectionUtils.protect(moduleManager.getModules()); } @Override public Collection<ModuleDescription> getModulesFromRepository() { return getRepository().getModules(); } @Override public Repository getRepository() { return RepositoryReader.getCachedRepository(core.getApplication().getRepository()); } @Override public void registerSwingLoader(String moduleId, SwingLoader swingLoader) { loaders.put(moduleId, swingLoader); } @Override public boolean needTwoPhasesLoading(Module module) { return module.isCollection() && !SimplePropertiesCache.get("collectionChosen", Boolean.class); } @Override public void startModule(Module module) throws ModuleException { SwingUtils.assertNotEDT("startModule(Module)"); LoggerFactory.getLogger(getClass()).debug("Start module {}", module.getBundle().getSymbolicName()); if (needTwoPhasesLoading(module)) { throw new IllegalStateException("The module needs a collection"); } synchronized (getModuleLock(module)) { if (module.getState() == STARTED) { throw new IllegalStateException("The module is already started. "); } try { loadImageResources(module); moduleManager.startModule(module); } catch (ModuleException e) { unloadImageResources(module); throw e; } setState(module, STARTED); SwingLoader loader = loaders.remove(module.getId()); if (loader != null) { loader.afterAll(); } } fireModuleStarted(module); LoggerFactory.getLogger(getClass()).debug("Module {} started", module.getBundle().getSymbolicName()); } /** * Unload the image resources of the module. * * @param module The module to unload the image resources. */ private void unloadImageResources(Module module) { for (ImageResource imageResource : resources.get(module).getImageResources()) { imageService.releaseResource(imageResource.getName()); } } /** * Load the image resources of the module. * * @param module The module to load the image resources for. */ private void loadImageResources(Module module) { for (ImageResource imageResource : resources.get(module).getImageResources()) { String resource = imageResource.getResource(); if (resource.startsWith("classpath:")) { imageService.registerResource(imageResource.getName(), new UrlResource(module.getBundle().getResource(resource.substring(10)))); } } } @Override public void stopModule(Module module) throws ModuleException { SwingUtils.assertNotEDT("stopModule(Module)"); LoggerFactory.getLogger(getClass()).debug("Stop module {}", module.getBundle().getSymbolicName()); synchronized (getModuleLock(module)) { if (module.getState() != STARTED) { throw new IllegalStateException("The module is not started. "); } moduleManager.stopModule(module); unloadImageResources(module); fireModuleStopped(module); ModuleResourceCache.removeModule(module.getId()); setState(module, INSTALLED); } LoggerFactory.getLogger(getClass()).debug("Module {} has been stopped", module.getBundle().getSymbolicName()); } @Override public void enableModule(Module module) { synchronized (getModuleLock(module)) { if (module.getState() == DISABLED) { setState(module, INSTALLED); } } } @Override public void disableModule(Module module) throws ModuleException { synchronized (getModuleLock(module)) { if (module.getState() == STARTED) { stopModule(module); } setState(module, DISABLED); } } @Override public void installModule(File file) throws ModuleException { Module module = moduleManager.installModule(file); if (module != null) { installModule(module); } } /** * Install the given module. * * @param module The module to install. */ private void installModule(Module module) { configuration.update(module); fireModuleInstalled(module); } @Override public void installFromRepository(String jarFile) throws ModuleException { installModule(moduleManager.installModuleFromRepository(new File(Folders.getModulesFolder(), jarFile))); } @Override public void uninstallModule(Module module) throws ModuleException { synchronized (getModuleLock(module)) { if (module.getState() == STARTED) { stopModule(module); } moduleManager.uninstallModule(module); configuration.remove(module); fireModuleUninstalled(module); for (ModuleListener listener : ModuleResourceCache.getResources(module.getId(), ModuleListener.class)) { listeners.remove(listener); } ModuleResourceCache.removeModule(module.getId()); } } @Override public void addModuleListener(String moduleId, ModuleListener listener) { listeners.add(listener); ModuleResourceCache.addResource(moduleId, ModuleListener.class, listener); } @Override public String canBeStarted(Module module) { synchronized (getModuleLock(module)) { if (module.getCoreVersion() != null && module.getCoreVersion().isGreaterThan(Core.VERSION)) { return "modules.message.version.problem"; } if (!moduleManager.areAllDependenciesSatisfiedAndActive(module)) { return "error.module.not.loaded.dependency"; } return ""; } } @Override public String canBeStopped(Module module) { synchronized (getModuleLock(module)) { if (module.getState() != STARTED) { return "error.module.not.started"; } if (moduleManager.isThereIsActiveDependenciesOn(module)) { return "error.module.dependencies"; } return ""; } } @Override public String canBeUninstalled(Module module) { synchronized (getModuleLock(module)) { if (module.getState() == STARTED && moduleManager.isThereIsActiveDependenciesOn(module)) { return "error.module.dependencies"; } return ""; } } @Override public String canBeDisabled(Module module) { synchronized (getModuleLock(module)) { if (module.getState() == DISABLED) { return "error.module.not.enabled"; } if (module.getState() == STARTED && moduleManager.isThereIsActiveDependenciesOn(module)) { return "error.module.dependencies"; } return ""; } } @Override public Module getModuleById(String id) { return moduleManager.getModuleById(id); } @Override public boolean isInstalled(String id) { return moduleManager.exists(id); } @Override public boolean hasCollectionModule() { return collectionModule; } /** * Set the state of a module. Not thread safe, must be called with a lock on getModuleLock(module) * * @param module The module to set the state. * @param state The state. */ private void setState(Module module, ModuleState state) { module.setState(state); configuration.update(module); } /** * Return the lock for the module. * * @param module The module to get the lock for. * * @return The lock of the module. */ private Object getModuleLock(Module module) { moduleLocks.putIfAbsent(module.getId(), new Object()); return moduleLocks.get(module.getId()); } /** * Fire a module started event. * * @param module The started module. */ private void fireModuleStarted(Module module) { for (ModuleListener listener : listeners) { listener.moduleStarted(module); } } /** * Fire a module stopped event. * * @param module The stopped module. */ private void fireModuleStopped(Module module) { for (ModuleListener listener : listeners) { listener.moduleStopped(module); } } /** * Fire a module installed event. * * @param module The installed module. */ private void fireModuleInstalled(Module module) { for (ModuleListener listener : listeners) { listener.moduleInstalled(module); } } /** * Fire a module uninstalled event. * * @param module The uninstalled module. */ private void fireModuleUninstalled(Module module) { for (ModuleListener listener : listeners) { listener.moduleUninstalled(module); } } /** * Return the resources of the given module. * * @param module The module to get the resources for. * * @return The ModuleResources of the module. */ ModuleResources getResources(Module module) { return resources.get(module); } /** * Set the resources of the module. * * @param module The module. * @param resources The module resources. */ void setResources(Module module, ModuleResources resources) { this.resources.put(module, resources); } }