/* * * This file is part of the Hesperides distribution. * * (https://github.com/voyages-sncf-technologies/hesperides) * * Copyright (c) 2016 VSCT. * * * * Hesperides is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as * * published by the Free Software Foundation, version 3. * * * * Hesperides is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ package com.vsct.dt.hesperides.templating.modules; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import com.google.common.eventbus.EventBus; import com.google.common.eventbus.Subscribe; import com.vsct.dt.hesperides.exception.runtime.MissingResourceException; import com.vsct.dt.hesperides.storage.EventStore; import com.vsct.dt.hesperides.storage.HesperidesCommand; import com.vsct.dt.hesperides.storage.AbstractThreadAggregate; import com.vsct.dt.hesperides.storage.UserProvider; import com.vsct.dt.hesperides.templating.models.HesperidesPropertiesModel; import com.vsct.dt.hesperides.templating.models.Models; import com.vsct.dt.hesperides.templating.modules.cache.ModuleStoragePrefixInterface; import com.vsct.dt.hesperides.templating.modules.event.*; import com.vsct.dt.hesperides.templating.modules.template.Template; import com.vsct.dt.hesperides.templating.modules.template.TemplateData; import com.vsct.dt.hesperides.templating.modules.template.TemplateRegistryInterface; import com.vsct.dt.hesperides.templating.packages.AbstractTemplatePackagesAggregate; import com.vsct.dt.hesperides.util.HesperidesVersion; import com.vsct.dt.hesperides.util.Release; import com.vsct.dt.hesperides.util.WorkingCopy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.function.Consumer; import java.util.stream.Collectors; /** * Service used to manage modules and related templates. * Templates are loosely tied to modules (only via the namespace attributes). * This helps evolving the model defining how templates are tied together. * Created by william_montaz on 02/12/2014. */ public abstract class AbstractModulesAggregate extends AbstractThreadAggregate implements Modules, ModuleEventInterface, ModuleStoragePrefixInterface { private static final Logger LOGGER = LoggerFactory.getLogger(AbstractModulesAggregate.class); /** * Name of Aggregate */ protected static final String NAME = "Modules"; /** * Constructor using no particular UserProvider (used when there was no authentication) * @param eventBus {@link EventBus} used to propagate events * @param eventStore {@link EventStore} used to store events */ public AbstractModulesAggregate(final EventBus eventBus, final EventStore eventStore) { super(eventBus, eventStore); } /** * Constructor using a specific UserProvider * @param eventBus {@link EventBus} used to propagate events * @param eventStore {@link EventStore} used to store events * @param userProvider {@link UserProvider} used to get information about current user */ public AbstractModulesAggregate(final EventBus eventBus, final EventStore eventStore, final UserProvider userProvider) { super(eventBus, eventStore, userProvider); } /** * Convenient method used to apply actions on all modules (ex. Reindexation) * @param consumer {@link Consumer} performing actions on a single module */ public void withAll(Consumer<Module> consumer) { getModuleRegistry().getAllModules().stream() .forEach(valueObject -> consumer.accept(valueObject)); } /** * Convenient method used to apply actions on all templates. Templates are loosely tied to modules * @param consumer {@link Consumer} performing actions on a single template */ public void withAllTemplates(Consumer<Template> consumer) { getTemplateRegistry().allTemplates().stream() .forEach(valueObject -> consumer.accept(valueObject)); } /** * Get a set containing all modules (moduels are unique) * @return an {@link Set} of {@link Module}s */ @Override public Collection<Module> getAllModules() { return getModuleRegistry().getAllModules(); } /** * Get a set containing all template * @return an {@link java.util.Set} of {@link Template}s */ public Collection<Template> getAll() { return getTemplateRegistry().allTemplates(); } /** * Get immutable representation of a module * @param moduleKey {@link ModuleKey} describing module * @return an {@link Optional} of a {@link Module} */ public Optional<Module> getModule(final ModuleKey moduleKey) { return getModuleRegistry().getModule(moduleKey); } /** * Get all templates related to a module * @param moduleKey {@link ModuleKey} describing module * @return {@link List} of {@link Template} */ public List<Template> getAllTemplates(final ModuleKey moduleKey) { return getTemplateRegistry().getAllTemplates(moduleKey).stream() .collect(Collectors.toList()); } /** * Get a specific template in a module * @param moduleKey {@link ModuleKey} describing module * @param templateName Name of the template * @return an {@link Optional} of a {@link Template} */ public Optional<Template> getTemplate(final ModuleKey moduleKey, String templateName) { return getTemplateRegistry().getTemplate(moduleKey.getNamespace(), templateName); } /** * Returns the properties model of a module * It combines the model related to module specific tempaltes and the models related to technos * @param moduleKey @link com.vsct.dt.hesperides.templating.modules.ModuleKey} describing module * @return The {@link HesperidesPropertiesModel} */ public Optional<HesperidesPropertiesModel> getModel(final ModuleKey moduleKey) { if(! getModuleRegistry().existsModule(moduleKey)) return Optional.empty(); HesperidesPropertiesModel packageModel = getModels().getPropertiesModel(moduleKey.getNamespace()); final ImmutableSet<Techno> technos = this.getModule(moduleKey) .map(module -> ImmutableSet.copyOf(module.getTechnos())) .orElse(ImmutableSet.<Techno>of()); for (final Techno techno : technos) { final HesperidesPropertiesModel model = getTemplatePackages().getModel(techno.getName(), techno.getVersion(), techno.isWorkingCopy()); packageModel = packageModel.merge(model); } return Optional.of(packageModel); } /** * Creates a working copy of a module * @param moduleSource {@link Module} representing the module * @return the {@link Module} created with its version ID */ public Module createWorkingCopy(final Module moduleSource) { //Make sure it is working copy final ModuleKey wcKey = new ModuleKey(moduleSource.getKey().getName(), WorkingCopy.of(moduleSource.getKey().getVersion())); return createModule(wcKey, moduleSource, Sets.newHashSet()); } /** * Updates a working copy of the module. * This method only updates the module object and not the templates related thus it is not possible to update fields representing the key of the module * @param module {@link Module} represneting the module * @return The updated {@link Module} with its new version ID */ public Module updateWorkingCopy(final Module module) { //Make sure to search the working copy final ModuleKey wcInfo = new ModuleKey(module.getKey().getName(), WorkingCopy.of(module.getKey().getVersion())); final HesperidesCommand<ModuleWorkingCopyUpdatedEvent> hc = new ModuleWorkingCopyUpdatedCommand(getModuleRegistry(), wcInfo, module); final ModuleWorkingCopyUpdatedEvent workingCopyUpdatedEvent = this.tryAtomic(wcInfo.getEntityName(), hc); return workingCopyUpdatedEvent.getUpdated(); } /** * Updates a template in a module working copy * Version id provided in tempalte data has to be the same as the actual version id of the template * NB: Separation of module version id and template version id is questionnable as it does not really provide optimistic locking on the module object itself * Since it doesn't lead to any problems right now, no use to change it * @param moduleKey {@link ModuleWorkingCopyKey} describing the module working copy * @param templateData Might need to change for TemplateVO * @return {@link Template} with its new version ID */ public Template updateTemplateInWorkingCopy(final ModuleWorkingCopyKey moduleKey, final TemplateData templateData) { Template.validateContent(templateData.getContent()); final ModuleTemplateUpdatedCommand hc = new ModuleTemplateUpdatedCommand(getTemplateRegistry(), moduleKey, templateData); final ModuleTemplateUpdatedEvent moduleTemplateUpdatedEvent = this.tryAtomic(moduleKey.getEntityName(), hc); return moduleTemplateUpdatedEvent.getUpdated(); } /** * Creates a tempalte in a module working copy * @param moduleKey {@link ModuleWorkingCopyKey} describing the module working copy * @param templateData Might need to change for TemplateVO * @return {@link Template} with its new version ID */ public Template createTemplateInWorkingCopy(final ModuleWorkingCopyKey moduleKey, final TemplateData templateData) { final ModuleKey wcInfo = new ModuleKey(moduleKey.getName(), WorkingCopy.of(moduleKey.getVersion())); Template.validateContent(templateData.getContent()); final ModuleTemplateCreatedCommand hc = new ModuleTemplateCreatedCommand(getTemplateRegistry(), wcInfo, templateData); final ModuleTemplateCreatedEvent moduleTemplateCreatedEvent = this.tryAtomic(wcInfo.getEntityName(), hc); return moduleTemplateCreatedEvent.getCreated(); } /** * Removes a template in a module working copy * @param moduleKey {@link ModuleWorkingCopyKey} describing the module working copy * @param templateName */ public void deleteTemplateInWorkingCopy(final ModuleWorkingCopyKey moduleKey, final String templateName) { final ModuleKey wcInfo = new ModuleKey(moduleKey.getName(), WorkingCopy.of(moduleKey.getVersion())); final ModuleTemplateDeletedCommand hc = new ModuleTemplateDeletedCommand(getTemplateRegistry(), wcInfo, templateName); this.tryAtomic(wcInfo.getEntityName(), hc); } /** * Creates a working copy of a module from another module * Templates are also copied from the source module * @param newModuleKey {@link ModuleWorkingCopyKey} describing the module working copy * @param fromModuleKey {@link ModuleKey} describing the module to copy from * @return */ public Module createWorkingCopyFrom(final ModuleWorkingCopyKey newModuleKey, final ModuleKey fromModuleKey) { return createModuleFrom(newModuleKey, fromModuleKey); } /** * Creates a release from an existing working copy * Templates are copied * @param fromModuleKey {@link ModuleWorkingCopyKey} describing the module working copy to create the release from * @param releaseVersion The version of the release. This can be useful to transform a 1.0-SNAPSHOT working copy to a 1.0 release * @return */ public Module createRelease(final ModuleWorkingCopyKey fromModuleKey, final String releaseVersion) { final ModuleKey releaseKey = new ModuleKey(fromModuleKey.getName(), Release.of(releaseVersion)); return createModuleFrom(releaseKey, fromModuleKey); } /** * Create a module from another module * The operation is not really atomic since the module and the related templates could have been changed while the method execute * It is not likely to happened due to the low charge of hesperides * @param newModuleKey * @param fromModuleKey * @return the freshly created module */ private Module createModuleFrom(final ModuleKey newModuleKey, final ModuleKey fromModuleKey) { final Module fromModule = getModuleRegistry().getModule(fromModuleKey).orElseThrow( () -> new MissingResourceException("There is no module " + fromModuleKey + " to build " + newModuleKey) ); //Get templates final Set<Template> templatesFrom = getTemplateRegistry().getAllTemplatesForNamespace(fromModuleKey.getNamespace()); //Be sure to create the event return this.createModule(newModuleKey, fromModule, templatesFrom); } /** * Creates a new module with a set of templates * @param newModuleKey * @param moduleSource * @param templatesFrom Set of {@link Template} to add to the module created * @return the freshly created module */ private Module createModule(final ModuleKey newModuleKey, final Module moduleSource, final Set<Template> templatesFrom) { final ModuleCreatedCommand hc = new ModuleCreatedCommand(getModuleRegistry(), getTemplateRegistry(), newModuleKey, moduleSource, templatesFrom); final ModuleCreatedEvent moduleCreatedEvent = this.tryAtomic(newModuleKey.getEntityName(), hc); return moduleCreatedEvent.getModuleCreated(); } /** * Deletes a module and all related templates * The module underlying stream is not actually deleted, just an event is posted * @param moduleKey {@link ModuleKey} to delete */ public void delete(final ModuleKey moduleKey){ final ModuleDeletedCommand hc = new ModuleDeletedCommand(getModuleRegistry(), getTemplateRegistry(), moduleKey); this.tryAtomic(moduleKey.getEntityName(), hc); } /* REPLAY METHODS */ @Subscribe public void replayModuleCreatedEvent(final ModuleCreatedEvent event) { try { final Module module = event.getModuleCreated(); final ModuleKey key = module.getKey(); this.createModule(key, module, event.getTemplates()); } catch (Exception e) { LOGGER.error("Error while replaying module created event {}", e.getMessage()); } } @Subscribe public void replayModuleWorkingCopyUpdatedEvent(final ModuleWorkingCopyUpdatedEvent event) { try { final Module module = event.getUpdated(); final Module withDecrementedVersionId = new Module(module.getKey(), module.getTechnos(), module.getVersionID() - 1); this.updateWorkingCopy(withDecrementedVersionId); } catch (Exception e) { LOGGER.error("Error while replaying module working copy updated event {}", e.getMessage()); } } @Subscribe public void replayModuleTemplateCreatedEvent(final ModuleTemplateCreatedEvent event) { try { final ModuleWorkingCopyKey key = new ModuleWorkingCopyKey(event.getModuleName(), event.getModuleVersion()); TemplateData templateData = TemplateData.withTemplateName(event.getCreated().getName()) .withFilename(event.getCreated().getFilename()) .withLocation(event.getCreated().getLocation()) .withContent(event.getCreated().getContent()) .withRights(event.getCreated().getRights()) .build(); this.createTemplateInWorkingCopy(key, templateData); } catch (Exception e) { LOGGER.error("Error while replaying module template created event {}", e.getMessage()); } } @Subscribe public void replayModuleTemplateUpdatedEvent(final ModuleTemplateUpdatedEvent event) { try { final ModuleWorkingCopyKey key = new ModuleWorkingCopyKey(event.getModuleName(), event.getModuleVersion()); final TemplateData templateData = TemplateData.withTemplateName(event.getUpdated().getName()) .withFilename(event.getUpdated().getFilename()) .withLocation(event.getUpdated().getLocation()) .withContent(event.getUpdated().getContent()) .withRights(event.getUpdated().getRights()) .withVersionID(event.getUpdated().getVersionID() - 1) .build(); this.updateTemplateInWorkingCopy(key, templateData); } catch (Exception e) { LOGGER.error("Error while replaying module template updated event {}", e.getMessage()); } } @Subscribe public void replayModuleTemplateDeletedEvent(final ModuleTemplateDeletedEvent event) { try { final ModuleWorkingCopyKey key = new ModuleWorkingCopyKey(event.getModuleName(), event.getModuleVersion()); this.deleteTemplateInWorkingCopy(key, event.getTemplateName()); } catch (Exception e) { LOGGER.error("Error while replaying module template deleted event {}", e.getMessage()); } } @Subscribe public void replayModuleDeletedEvent(final ModuleDeletedEvent event) { try { final HesperidesVersion version = event.isWorkingCopy() ? WorkingCopy.of(event.getModuleVersion()) : Release.of(event.getModuleVersion()); final ModuleKey moduleKey = new ModuleKey(event.getModuleName(), version); this.delete(moduleKey); } catch (Exception e) { LOGGER.error("Error while replaying module deleted event {}", e.getMessage()); } } protected abstract ModuleRegistryInterface getModuleRegistry(); protected abstract TemplateRegistryInterface getTemplateRegistry(); protected abstract AbstractTemplatePackagesAggregate getTemplatePackages(); protected abstract Models getModels(); }