/*
* Copyright 2016 ThoughtWorks, Inc.
*
* 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.
*/
package com.thoughtworks.go.server.materials;
import com.thoughtworks.go.config.CruiseConfig;
import com.thoughtworks.go.config.PipelineConfig;
import com.thoughtworks.go.config.materials.dependency.DependencyMaterial;
import com.thoughtworks.go.config.materials.dependency.DependencyMaterialConfig;
import com.thoughtworks.go.domain.Stage;
import com.thoughtworks.go.domain.StageResult;
import com.thoughtworks.go.domain.materials.Material;
import com.thoughtworks.go.listener.ConfigChangedListener;
import com.thoughtworks.go.listener.EntityConfigChangedListener;
import com.thoughtworks.go.server.domain.StageStatusListener;
import com.thoughtworks.go.server.initializers.Initializer;
import com.thoughtworks.go.server.service.GoConfigService;
import com.thoughtworks.go.server.service.MaterialConfigConverter;
import com.thoughtworks.go.serverhealth.HealthStateScope;
import com.thoughtworks.go.serverhealth.ServerHealthService;
import org.apache.commons.collections.CollectionUtils;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.*;
import static com.thoughtworks.go.serverhealth.HealthStateType.general;
import static com.thoughtworks.go.serverhealth.ServerHealthState.error;
import static java.lang.String.format;
/**
* Listens to Stage/Config changes and notifies MaterialUpdateService to update DependencyMaterial
*/
@Component
public class DependencyMaterialUpdateNotifier implements StageStatusListener, ConfigChangedListener, Initializer, MaterialUpdateCompleteListener {
private static final Logger LOGGER = Logger.getLogger(DependencyMaterialUpdateNotifier.class);
private final GoConfigService goConfigService;
private final MaterialConfigConverter materialConfigConverter;
private final MaterialUpdateService materialUpdateService;
private ServerHealthService serverHealthService;
private boolean skipUpdate = false;
private volatile Map<String, Material> dependencyMaterials;
private Set<Material> retryQueue = Collections.synchronizedSet(new HashSet<Material>());
@Autowired
public DependencyMaterialUpdateNotifier(GoConfigService goConfigService, MaterialConfigConverter materialConfigConverter,
MaterialUpdateService materialUpdateService, ServerHealthService serverHealthService) {
this.goConfigService = goConfigService;
this.materialConfigConverter = materialConfigConverter;
this.materialUpdateService = materialUpdateService;
this.serverHealthService = serverHealthService;
}
public void initialize() {
this.dependencyMaterials = dependencyMaterials();
goConfigService.register(this);
goConfigService.register(pipelineConfigChangedListener());
materialUpdateService.registerMaterialUpdateCompleteListener(this);
updateMaterialsOnIntialization();
}
@Override
public void onMaterialUpdate(Material material) {
if (material instanceof DependencyMaterial) {
if (retryQueue.remove(material)) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(format("[Material Update] Retrying update of dependency material %s ", material));
}
updateMaterial(material);
}
}
}
public void stageStatusChanged(Stage stage) {
if (StageResult.Passed == stage.getResult()) {
Material material = dependencyMaterials.get(stageIdentifier(stage.getIdentifier().getPipelineName(), stage.getName()));
if (material != null) {
updateMaterial(material);
}
}
}
@Override
public void onConfigChange(CruiseConfig newCruiseConfig) {
scheduleRecentlyAddedMaterialsForUpdate();
}
protected EntityConfigChangedListener<PipelineConfig> pipelineConfigChangedListener() {
final DependencyMaterialUpdateNotifier self = this;
return new EntityConfigChangedListener<PipelineConfig>() {
@Override
public void onEntityConfigChange(PipelineConfig pipelineConfig) {
self.scheduleRecentlyAddedMaterialsForUpdate();
}
};
}
// for integration tests
public void disableUpdates() {
this.skipUpdate = true;
}
// for integration tests
public void enableUpdates() {
this.skipUpdate = false;
}
private void updateMaterial(Material material) {
if(skipUpdate) return;
try {
if (!materialUpdateService.updateMaterial(material)) {
retryQueue.add(material);
}
} catch (Exception e) {
HealthStateScope scope = HealthStateScope.forMaterialUpdate(material);
serverHealthService.update(error(format("Error updating Dependency Material %s", material.getUriForDisplay()), e.getMessage(), general(scope)));
retryQueue.add(material);
}
}
private void updateMaterialsOnIntialization() {
for (Material material : this.dependencyMaterials.values()) {
updateMaterial(material);
}
}
private void scheduleRecentlyAddedMaterialsForUpdate() {
Collection<Material> materialsBeforeConfigChange = dependencyMaterials.values();
this.dependencyMaterials = dependencyMaterials();
Collection<Material> materialsAfterConfigChange = dependencyMaterials.values();
Collection newMaterials = CollectionUtils.subtract(materialsAfterConfigChange, materialsBeforeConfigChange);
for (Object material : newMaterials) {
updateMaterial((Material) material);
}
}
private HashMap<String, Material> dependencyMaterials() {
HashMap<String, Material> map = new HashMap<>();
for (DependencyMaterialConfig materialConfig : goConfigService.getSchedulableDependencyMaterials()) {
String stageIdentifier = stageIdentifier(materialConfig.getPipelineName().toString(), materialConfig.getStageName().toString());
map.put(stageIdentifier, materialConfigConverter.toMaterial(materialConfig));
}
return map;
}
private String stageIdentifier(String pipelineName, String stageName) {
return String.format("%s[%s]", pipelineName.toLowerCase(), stageName.toLowerCase());
}
}