/*
*
* * 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.applications;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import com.vsct.dt.hesperides.applications.cache.ApplicationStoragePrefixInterface;
import com.vsct.dt.hesperides.applications.event.*;
import com.vsct.dt.hesperides.applications.properties.PropertiesRegistryInterface;
import com.vsct.dt.hesperides.exception.runtime.MissingResourceException;
import com.vsct.dt.hesperides.storage.EventStore;
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.platform.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.stream.Collectors;
/**
* Created by emeric_martineau on 27/05/2016.
*/
public abstract class AbstractApplicationsAggregate extends AbstractThreadAggregate implements Applications, PlatformEventBuilderInterface, ApplicationStoragePrefixInterface {
private static final Logger LOGGER = LoggerFactory.getLogger(ApplicationsAggregate.class);
/**
* Name of Aggregate
*/
protected static final String NAME = "Applications";
/**
* Constructor to be used.
*
* @param eventBus
* @param eventStore
*/
public AbstractApplicationsAggregate(final EventBus eventBus, final EventStore eventStore) {
super(eventBus, eventStore);
}
/**
* Constructor to be used.
*
* @param eventBus
* @param eventStore
* @param userProvider
*/
public AbstractApplicationsAggregate(final EventBus eventBus, final EventStore eventStore,
final UserProvider userProvider) {
super(eventBus, eventStore, userProvider);
}
/**
* Plateform registry.
*
* @return
*/
protected abstract PlatformRegistryInterface getPlatformRegistry();
/**
* Properties registry.
*
* @return
*/
protected abstract PropertiesRegistryInterface getPropertiesRegistry();
/**
* Snapshot registry.
*
* @return
*/
protected abstract SnapshotRegistryInterface getSnapshotRegistry();
/**
* Get an application with its name.
* The application is not actually stored so we create it by assembling all the platforms corresponding to that application
*
* @param applicationName
* @return the matching application or empty
*/
@Override
public Optional<ApplicationData> getApplication(final String applicationName) {
final List<PlatformData> platforms = getPlatformRegistry().getPlatformsForApplication(applicationName);
if (platforms.size() > 0) {
return Optional.of(new ApplicationData(
applicationName,
platforms
));
} else {
return Optional.empty();
}
}
/**
* Get a platform with its name and its application name.
*
* @param platformKey
* @return The corresponding platform or empty
*/
@Override
public Optional<PlatformData> getPlatform(final PlatformKey platformKey) {
return getPlatformRegistry().getPlatform(platformKey);
}
/**
* Get a platform at a specific moment in time defined by timestamp param
*
* @param platformKey
* @param timestamp
* @return
*/
@Override
public Optional<TimeStampedPlatformData> getPlatform(final PlatformKey platformKey, final long timestamp) {
final Optional<PlatformData> plt = getPlatformRegistry().getPlatform(
new PlatformTimelineKey(platformKey, timestamp));
if (plt.isPresent()) {
return Optional.of(TimeStampedPlatformData
.withPlatform(plt.get())
.withTimestamp(timestamp).build());
} else {
return Optional.empty();
}
}
/**
* Creates a platform with given modules.
* It will provide an id to all modules that have no id or id set to 0
*
* @param platform
* @return the created platform value object (with a versionID at 1)
*/
@Override
public PlatformData createPlatform(final PlatformData platform) {
final PlatformCreatedCommand hc = new PlatformCreatedCommand(getPlatformRegistry(), platform);
final PlatformCreatedEvent createdPlatformEventTry = this.tryAtomic(platform.getKey().getEntityName(), hc);
return createdPlatformEventTry.getPlatform();
}
/**
* Method to create a platform from another
* We also need to copy the properties
* This implementation is not atomic and creates as many events as if we did the operation manually by single steps
* It should be sufficient for the use case, but it can fail if the new platform is concurrently modified, which is pretty much impossible
*
* @param platform
* @param fromPlatformKey
* @return
*/
@Override
public PlatformData createPlatformFromExistingPlatform(final PlatformData platform, final PlatformKey fromPlatformKey) {
// Getting existing platform
PlatformData existingPlatform = getPlatform(fromPlatformKey).orElseThrow(() -> new MissingResourceException("There is no existing platform " + fromPlatformKey + " to build from"));
// new platform's key
PlatformKey key = platform.getKey();
// Building the platform to be created, this contains all the stuff
PlatformData newPlatformToBeCreated = PlatformData.withPlatformName(key.getName())
.withApplicationName(key.getApplicationName())
.withApplicationVersion(platform.getApplicationVersion())
.withModules(existingPlatform.getModules())
.withVersion(1L)
.setProduction(platform.isProduction())
.build();
// Get the properties by path
Map<String, PropertiesData> existingPropertiesByPath = getPropertiesRegistry().getProperties(fromPlatformKey.getApplicationName(), fromPlatformKey.getName());
return createPlatformFromExistingPlatformHandler(newPlatformToBeCreated, existingPlatform, existingPropertiesByPath);
}
/**
* Handler
* @param platform
* @return
*/
private PlatformData createPlatformFromExistingPlatformHandler(final PlatformData platform,
final PlatformData originPlatform,
final Map<String, PropertiesData> originProperties){
final PlatformCreatedFromExistingCommand hc = new PlatformCreatedFromExistingCommand(getPlatformRegistry(),
getPropertiesRegistry(), platform, originPlatform, originProperties);
this.tryAtomic(platform.getKey().getEntityName(), hc);
//We return the platform from get method, this way we are sure to get the platform from the registry, ie the way it has been modified.
return getPlatform(platform.getKey()).orElseThrow(() -> new MissingResourceException("Cannot get the created platform. This is not expected and should be reported"));
}
@Override
public PlatformData updatePlatform(final PlatformData platform, final boolean isCopyingPropertiesForUpdatedModules) {
final PlatformUpdatedCommand hc =
new PlatformUpdatedCommand(getPlatformRegistry(), getPropertiesRegistry(), platform,
isCopyingPropertiesForUpdatedModules);
final PlatformUpdatedEvent updatedPlatformEventTry = this.tryAtomic(platform.getKey().getEntityName(), hc);
return updatedPlatformEventTry.getPlatform();
}
/**
* Get properties for a platform with the specific path (example GSTWDI#WAS).
*
* @param platformKey
* @param path
* @return the properties or an empty property wrapper if none found
*/
public PropertiesData getProperties(final PlatformKey platformKey, final String path) {
return getPropertiesRegistry().getProperties(platformKey.getApplicationName(), platformKey.getName(), path).orElse(PropertiesData.empty());
}
@Override
public PropertiesData getProperties(final PlatformKey platformKey, final String path, final long timestamp) {
return getPropertiesRegistry().getProperties(platformKey.getApplicationName(), platformKey.getName(), path,
timestamp).orElse(PropertiesData.empty()); }
/**
* Create or Update properties for a platform at the given path.
*
* @param platformKey
* @param path
* @param properties
* @param platformVersionId
* @return the properties value object
*/
@Override
public PropertiesData createOrUpdatePropertiesInPlatform(final PlatformKey platformKey, final String path,
final PropertiesData properties,
final long platformVersionId, final String comment) {
final PropertiesSavedCommand hc = new PropertiesSavedCommand(getPlatformRegistry(), getPropertiesRegistry(),
platformKey, path, properties, platformVersionId, comment);
final PropertiesSavedEvent propertiesSavedEvent = this.tryAtomic(platformKey.getEntityName(), hc);
return propertiesSavedEvent.getProperties();
}
/**
* Find the model of properties to be evaluated for a given instance.
*
* @param platformKey
* @param propertiesPath
* @return the corresponding model
*/
@Override
public InstanceModel getInstanceModel(final PlatformKey platformKey, final String propertiesPath) {
final String applicationName = platformKey.getApplicationName();
final String platformName = platformKey.getName();
this.getPlatform(platformKey).orElseThrow(() -> new MissingResourceException("Application/Platform " + applicationName + "/" + platformName + " does not exist"));
final PropertiesData properties = this.getProperties(platformKey, propertiesPath);
final PropertiesData globalProperties = this.getProperties(platformKey, "#");
return properties.generateInstanceModel(globalProperties.getKeyValueProperties());
}
@Override
public void delete(final PlatformKey key) {
final PlatformDeletedCommand hc = new PlatformDeletedCommand(getPlatformRegistry(), getPropertiesRegistry(), key);
this.tryAtomic(key.getEntityName(), hc);
}
/**
* Take a snapshot of the platform, the timestamp will be System.currentMillisecond
*
* @param key
* @return the timestamp of the snapshot
*/
@Override
public long takeSnapshot(PlatformKey key) {
final long timestamp = System.currentTimeMillis();
return takeSnapshot(key, timestamp);
}
/**
* Used to replay the event specifying a timestamp
*
* @return
*/
private long takeSnapshot(PlatformKey key, long timestamp) {
final PlatformSnapshotCommand hc = new PlatformSnapshotCommand(getPlatformRegistry(), getPropertiesRegistry(),
getSnapshotRegistry(), key, timestamp);
final PlatformSnapshotEvent snapshotEvent = this.tryAtomic(key.getEntityName(), hc);
return snapshotEvent.getTimestamp();
}
@Override
public PlatformData restoreSnapshot(PlatformKey key, long timestamp) {
final PlatformSnapshotKey snapshotKey = new PlatformSnapshotKey(timestamp, key);
final Optional<PlatformSnapshot> optionalSnapshot
= getSnapshotRegistry().getSnapshot(snapshotKey, PlatformSnapshot.class);
if (optionalSnapshot.isPresent()) {
return restoreSnapshot(timestamp, optionalSnapshot.get());
} else {
throw new MissingResourceException("Could not find snapshot " + timestamp + " for " + key);
}
}
private PlatformData restoreSnapshot(long timestamp, PlatformSnapshot snapshot) {
final PlatformKey key = snapshot.getPlatform().getKey();
final PlatformSnapshotRestoreCommand hc = new PlatformSnapshotRestoreCommand(getPlatformRegistry(),
getPropertiesRegistry(), timestamp, snapshot);
this.tryAtomic(key.getEntityName(), hc);
return getPlatformRegistry().getPlatform(key).get();
}
@Override
public List<Long> getSnapshots(PlatformKey key) {
// TODO Warning, Redis dependency !!! It's not good
final String pattern = "snapshot-platform-" + key.getApplicationName() + "-" + key.getName() + "-*";
final Set<String> snapshotKeysAsString = getSnapshotRegistry().getKeys(pattern);
return snapshotKeysAsString.stream()
.map(keyAsString -> Long.parseLong(keyAsString.replaceFirst(pattern, "")))
.sorted((longA, longB) -> longA.compareTo(longB))
.collect(Collectors.toList());
}
/*
* REPLAY LISTENERS
*/
@Subscribe
@Override
public void replayPlatformCreatedEvent(final PlatformCreatedEvent event) {
try {
final PlatformData platform = event.getPlatform();
this.createPlatform(platform);
} catch (Exception e) {
LOGGER.error("Error while replaying platform created event {}", e.getMessage());
}
}
@Subscribe
@Override
public void replayPlatformCreatedFromExistingEvent(final PlatformCreatedFromExistingEvent event) {
try {
PlatformData platform = event.getPlatform();
PlatformData originPlatform = event.getOriginPlatform();
Map<String, PropertiesData> orginProperties = event.getOriginProperties();
this.createPlatformFromExistingPlatformHandler(platform, originPlatform, orginProperties);
} catch (Exception e) {
LOGGER.error("Error while replaying platform created from existing event {}", e.getMessage());
}
}
@Subscribe
@Override
public void replayPlatformUpdatedEvent(final PlatformUpdatedEvent event) {
try {
final PlatformData platform = event.getPlatform();
final PlatformKey key = platform.getKey();
final PlatformData withDecrementedVersionID = PlatformData.withPlatformName(key.getName())
.withApplicationName(key.getApplicationName())
.withApplicationVersion(platform.getApplicationVersion())
.withModules(platform.getModules())
.withVersion(platform.getVersionID() - 1)
.setProduction(platform.isProduction())
.build();
this.updatePlatform(withDecrementedVersionID, event.isCopyingPropertiesForUpgradedModules());
} catch (Exception e) {
LOGGER.error("Error while replaying platform updated event {}", e.getMessage());
}
}
@Subscribe
@Override
public void replayPropertiesSavedEvent(final PropertiesSavedEvent event) {
try {
final PlatformKey platformKey = PlatformKey.withName(event.getPlatformName())
.withApplicationName(event.getApplicationName())
.build();
final PlatformData platform = this.getPlatform(platformKey).orElseThrow(() -> new RuntimeException("Cannot update properties in an unknown platform"));
PlatformKey.withName(event.getPlatformName())
.withApplicationName(event.getApplicationName())
.build();
this.createOrUpdatePropertiesInPlatform(platformKey, event.getPath(), event.getProperties(),
platform.getVersionID(), "PFR");
} catch (Exception e) {
LOGGER.error("Error while replaying properties saved event {}", e.getMessage());
}
}
@Subscribe
@Override
public void replayPlateformeDeletedEvent(final PlatformDeletedEvent event) {
try {
final PlatformKey platformKey = PlatformKey.withName(event.getPlatformName())
.withApplicationName(event.getApplicationName())
.build();
this.delete(platformKey);
} catch (Exception e) {
LOGGER.error("Error while replaying platform deleted event {}", e.getMessage());
}
}
@Subscribe
@Override
public void replaySnapshotTakenEvent(final PlatformSnapshotEvent event) {
try {
final PlatformKey platformKey = PlatformKey.withName(event.getPlatformName())
.withApplicationName(event.getApplicationName())
.build();
this.takeSnapshot(platformKey, event.getTimestamp());
} catch (Exception e) {
LOGGER.error("Error while replaying platform deleted event {}", e.getMessage());
}
}
@Subscribe
@Override
public void replaySnapshotRestoredEvent(final PlatformSnapshotRestoreEvent event) {
try {
this.restoreSnapshot(event.getTimestamp(), event.getSnapshot());
} catch (Exception e) {
LOGGER.error("Error while replaying snapshot restore event {}", e.getMessage());
}
}
/**
* Get a set containing all platforms
* @return an {@link java.util.Set} of {@link PlatformData}s
*/
public Collection<PlatformData> getAll() {
return getPlatformRegistry().getAllPlatforms();
}
@Override
public PropertiesData getSecuredProperties(PlatformKey platformKey, String path, HesperidesPropertiesModel model) {
// No security need for that
return this.getProperties(platformKey, path);
}
@Override
public PropertiesData getSecuredProperties(PlatformKey platformKey, String path, long timestamp, HesperidesPropertiesModel model) {
// Security need for that
return this.getProperties(platformKey, path, timestamp);
}
/**
* Gets the number of platforms
*
* @return {@link Integer}
*/
@Override
public Collection<PlatformData> getAllPlatforms() {
return getPlatformRegistry().getAllPlatforms();
}
}