/*************************GO-LICENSE-START*********************************
* Copyright 2014 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.
*************************GO-LICENSE-END***********************************/
package com.thoughtworks.go.server.materials;
import com.thoughtworks.go.config.materials.Materials;
import com.thoughtworks.go.config.materials.PackageMaterial;
import com.thoughtworks.go.config.materials.PluggableSCMMaterial;
import com.thoughtworks.go.config.materials.dependency.DependencyMaterial;
import com.thoughtworks.go.domain.MaterialInstance;
import com.thoughtworks.go.domain.MaterialRevisions;
import com.thoughtworks.go.domain.materials.Material;
import com.thoughtworks.go.domain.materials.Modifications;
import com.thoughtworks.go.server.persistence.MaterialRepository;
import com.thoughtworks.go.server.service.MaterialExpansionService;
import com.thoughtworks.go.server.transaction.TransactionCallback;
import com.thoughtworks.go.server.transaction.TransactionTemplate;
import com.thoughtworks.go.serverhealth.HealthStateScope;
import com.thoughtworks.go.serverhealth.HealthStateType;
import com.thoughtworks.go.serverhealth.ServerHealthService;
import com.thoughtworks.go.serverhealth.ServerHealthState;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionStatus;
import java.io.File;
/**
* @understands how to update materials on the database from the real SCMs
*/
@Component
public class MaterialDatabaseUpdater {
public static final String MATERIALS_MUTEX_FORMAT = MaterialDatabaseUpdater.class.getName() + "_MaterialMutex_%s_%s";
private static final Logger LOGGER = Logger.getLogger(MaterialDatabaseUpdater.class);
static final int STAGES_PER_PAGE = 100;
private final MaterialRepository materialRepository;
private final ServerHealthService healthService;
private TransactionTemplate transactionTemplate;
private final DependencyMaterialUpdater dependencyMaterialUpdater;
private final ScmMaterialUpdater scmMaterialUpdater;
private MaterialExpansionService materialExpansionService;
private PackageMaterialUpdater packageMaterialUpdater;
private PluggableSCMMaterialUpdater pluggableSCMMaterialUpdater;
@Autowired
public MaterialDatabaseUpdater(MaterialRepository materialRepository, ServerHealthService healthService, TransactionTemplate transactionTemplate,
DependencyMaterialUpdater dependencyMaterialUpdater, ScmMaterialUpdater scmMaterialUpdater, PackageMaterialUpdater packageMaterialUpdater,
PluggableSCMMaterialUpdater pluggableSCMMaterialUpdater, MaterialExpansionService materialExpansionService) {
this.materialRepository = materialRepository;
this.healthService = healthService;
this.transactionTemplate = transactionTemplate;
this.dependencyMaterialUpdater = dependencyMaterialUpdater;
this.scmMaterialUpdater = scmMaterialUpdater;
this.packageMaterialUpdater = packageMaterialUpdater;
this.pluggableSCMMaterialUpdater = pluggableSCMMaterialUpdater;
this.materialExpansionService = materialExpansionService;
}
public void updateMaterial(final Material material) throws Exception {
String materialMutex = mutexForMaterial(material);
HealthStateScope scope = HealthStateScope.forMaterial(material);
try {
MaterialInstance materialInstance = materialRepository.findMaterialInstance(material);
if (materialInstance == null) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(String.format("[Material Update] Material repository not found, creating with latest revision from %s", material));
}
synchronized (materialMutex) {
if (materialRepository.findMaterialInstance(material) == null) {
transactionTemplate.executeWithExceptionHandling(new TransactionCallback() {
@Override
public Object doInTransaction(TransactionStatus status) throws Exception {
initializeMaterialWithLatestRevision(material);
return null;
}
});
}
}
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(String.format("[Material Update] Existing material repository, fetching new revisions from %s in flyweight %s", material, materialInstance.getFlyweightName()));
}
synchronized (materialMutex) {
transactionTemplate.executeWithExceptionHandling(new TransactionCallback() {
@Override
public Object doInTransaction(TransactionStatus status) throws Exception {
updateMaterialWithNewRevisions(material);
return null;
}
});
}
}
healthService.removeByScope(scope);
} catch (Exception e) {
String message = "Modification check failed for material: " + material.getLongDescription();
String errorDescription = e.getMessage() == null ? "Unknown error" : e.getMessage();
healthService.update(ServerHealthState.error(message, errorDescription, HealthStateType.general(scope)));
LOGGER.warn(String.format("[Material Update] %s", message), e);
throw e;
}
}
private void initializeMaterialWithLatestRevision(Material material) {
Materials materials = new Materials();
materialExpansionService.expandForHistory(material, materials);
for (Material expanded : materials) {
addNewMaterialWithModifications(folderFor(expanded), expanded, updater(expanded));
}
}
void updateMaterialWithNewRevisions(Material material) {
Materials materials = new Materials();
materialExpansionService.expandForHistory(material, materials);
for (Material expanded : materials) {
MaterialInstance expandedInstance = materialRepository.findMaterialInstance(expanded);
File expandedFolder = folderFor(expanded);
if (expandedInstance == null) {
addNewMaterialWithModifications(expandedFolder, expanded, updater(expanded));
} else {
insertLatestOrNewModifications(expanded, expandedInstance, expandedFolder, updater(expanded));
}
}
}
private void insertLatestOrNewModifications(Material material, MaterialInstance materialInstance, File folder, MaterialUpdater updater) {
MaterialRevisions materialRevisions = materialRepository.findLatestModification(material);
Modifications list = materialRevisions.getModifications(material);
updater.insertLatestOrNewModifications(material, materialInstance, folder, list);
}
MaterialUpdater updater(Material material) {
if (material instanceof DependencyMaterial) {
return dependencyMaterialUpdater;
}
if (material instanceof PackageMaterial) {
return packageMaterialUpdater;
}
if (material instanceof PluggableSCMMaterial) {
return pluggableSCMMaterialUpdater;
}
return scmMaterialUpdater;
}
private File folderFor(Material material) {
return this.materialRepository.folderFor(material);
}
private void addNewMaterialWithModifications(File folder, Material expanded, MaterialUpdater updater) {
updater.addNewMaterialWithModifications(expanded, folder);
}
private String mutexForMaterial(Material material) {
if (material instanceof DependencyMaterial) {
DependencyMaterial dep = ((DependencyMaterial) material);
return String.format(MATERIALS_MUTEX_FORMAT, dep.getPipelineName().toLower(), dep.getStageName().toLower()).intern();
} else {
return String.format(MATERIALS_MUTEX_FORMAT, material.getFingerprint(), "-this-lock-should-not-be-acquired-by-anyone-else-inadvertently").intern();
}
}
}