/*
* 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.domain.packagerepository;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import javax.annotation.PostConstruct;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
import com.thoughtworks.go.config.*;
import com.thoughtworks.go.config.builder.ConfigurationPropertyBuilder;
import com.thoughtworks.go.config.materials.AbstractMaterialConfig;
import com.thoughtworks.go.config.validation.NameTypeValidator;
import com.thoughtworks.go.domain.ConfigErrors;
import com.thoughtworks.go.domain.ConfigurationDisplayUtil;
import com.thoughtworks.go.domain.config.Configuration;
import com.thoughtworks.go.domain.config.ConfigurationProperty;
import com.thoughtworks.go.domain.config.SecureKeyInfoProvider;
import com.thoughtworks.go.plugin.api.config.Property;
import com.thoughtworks.go.util.CachedDigestUtils;
import com.thoughtworks.go.util.StringUtil;
import com.thoughtworks.go.plugin.access.packagematerial.AbstractMetaDataStore;
import com.thoughtworks.go.plugin.access.packagematerial.PackageMetadataStore;
import com.thoughtworks.go.plugin.access.packagematerial.RepositoryMetadataStore;
import com.thoughtworks.go.plugin.access.packagematerial.PackageConfiguration;
import com.thoughtworks.go.plugin.access.packagematerial.PackageConfigurations;
import static com.thoughtworks.go.util.ListUtil.join;
import static java.lang.String.format;
import static org.apache.commons.lang.StringUtils.isBlank;
@ConfigTag("package")
@ConfigReferenceCollection(collectionName = "packages", idFieldName = "id")
public class PackageDefinition implements Serializable, Validatable, ParamsAttributeAware {
public static final String NAME = "name";
public static final String ID = "id";
public static final String CONFIGURATION = "configuration";
private final ConfigErrors errors = new ConfigErrors();
@ConfigAttribute(value = "id", allowNull = true)
private String id;
@ConfigAttribute(value = "name", allowNull = false)
private String name;
@ConfigAttribute(value = "autoUpdate", optional = true)
private boolean autoUpdate = true;
@Expose
@SerializedName("config")
@ConfigSubtag
private Configuration configuration = new Configuration();
@Expose
@SerializedName("repository")
@IgnoreTraversal
private PackageRepository packageRepository;
public PackageDefinition() {
}
public PackageDefinition(String id, String name, Configuration configuration) {
this.id = id;
this.name = name;
this.configuration = configuration;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isAutoUpdate() {
return autoUpdate;
}
public void setAutoUpdate(boolean autoUpdate) {
this.autoUpdate = autoUpdate;
}
public Configuration getConfiguration() {
return configuration;
}
public PackageRepository getRepository() {
return packageRepository;
}
public void setConfiguration(Configuration configuration) {
this.configuration = configuration;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
PackageDefinition that = (PackageDefinition) o;
if (configuration != null ? !configuration.equals(that.configuration) : that.configuration != null) {
return false;
}
if (id != null ? !id.equals(that.id) : that.id != null) {
return false;
}
if (name != null ? !name.equals(that.name) : that.name != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = id != null ? id.hashCode() : 0;
result = 31 * result + (name != null ? name.hashCode() : 0);
result = 31 * result + (configuration != null ? configuration.hashCode() : 0);
return result;
}
@Override
public void validate(ValidationContext validationContext) {
if (isBlank(name)) {
errors().add(NAME, "Package name is mandatory");
} else if (new NameTypeValidator().isNameInvalid(name)) {
errors().add(NAME, NameTypeValidator.errorMessage("Package", name));
}
configuration.validateUniqueness(String.format("Package '%s'", name));
}
public void validateFingerprintUniqueness(Map<String, Packages> packagesMap) {
String fingerprint = getFingerprint(AbstractMaterialConfig.FINGERPRINT_DELIMITER);
Packages packageDefinitionsWithSameFingerprint = packagesMap.get(fingerprint);
if (packageDefinitionsWithSameFingerprint.size() > 1) {
List<String> packageNames = new ArrayList<>();
for (PackageDefinition packageDefinition : packageDefinitionsWithSameFingerprint) {
packageNames.add(format("[Repo Name: '%s', Package Name: '%s']", packageDefinition.getRepository().getName(), packageDefinition.getName()));
}
addError(ID, String.format("Cannot save package or repo, found duplicate packages. %s", join(packageNames)));
}
}
@Override
public ConfigErrors errors() {
return errors;
}
public List<ConfigErrors> getAllErrors() {
return ErrorCollector.getAllErrors(this);
}
@Override
public void addError(String fieldName, String message) {
errors.add(fieldName, message);
}
public void setRepository(PackageRepository packageRepository) {
this.packageRepository = packageRepository;
}
public String getConfigForDisplay() {
AbstractMetaDataStore metadataStore = PackageMetadataStore.getInstance();
List<ConfigurationProperty> propertiesToBeUsedForDisplay = ConfigurationDisplayUtil.getConfigurationPropertiesToBeUsedForDisplay(metadataStore, pluginId(), configuration);
return format("%s - Package: %s", getRepository().getConfigForDisplay(), configuration.forDisplay(propertiesToBeUsedForDisplay));
}
public String getFingerprint(String fingerprintDelimiter) {
List<String> list = new ArrayList<>();
list.add(format("%s=%s", "plugin-id", pluginId()));
handlePackageDefinitionProperties(list);
handlePackageRepositoryProperties(list);
String fingerprint = join(list, fingerprintDelimiter);
// CAREFUL! the hash algorithm has to be same as the one used in 47_create_new_materials.sql
return CachedDigestUtils.sha256Hex(fingerprint);
}
private void handlePackageDefinitionProperties(List<String> list) {
PackageConfigurations metadata = PackageMetadataStore.getInstance().getMetadata(pluginId());
for (ConfigurationProperty configurationProperty : configuration) {
handleProperty(list, metadata, configurationProperty);
}
}
private String pluginId() {
return packageRepository.getPluginConfiguration().getId();
}
public void addConfigurations(List<ConfigurationProperty> configurations) {
ConfigurationPropertyBuilder builder = new ConfigurationPropertyBuilder();
for (ConfigurationProperty property : configurations) {
if (doesPluginExist()) {
com.thoughtworks.go.plugin.api.material.packagerepository.PackageConfiguration packageMetadata = PackageMetadataStore.getInstance().getPackageMetadata(pluginId());
if (isValidConfiguration(property.getConfigKeyName(), packageMetadata)) {
configuration.add(builder.create(property.getConfigKeyName(), property.getConfigValue(), property.getEncryptedValue(), packagePropertyFor(property.getConfigKeyName(), packageMetadata).getOption(Property.SECURE)));
} else {
configuration.add(property);
}
} else {
configuration.add(property);
}
}
}
private boolean isValidConfiguration(String configKeyName, com.thoughtworks.go.plugin.api.material.packagerepository.PackageConfiguration packageMetadata) {
return packagePropertyFor(configKeyName, packageMetadata) != null;
}
private Property packagePropertyFor(String configKeyName, com.thoughtworks.go.plugin.api.material.packagerepository.PackageConfiguration packageMetadata) {
return packageMetadata.get(configKeyName);
}
private boolean doesPluginExist() {
return packageRepository != null && RepositoryMetadataStore.getInstance().hasPlugin(pluginId());
}
private void handlePackageRepositoryProperties(List<String> list) {
PackageConfigurations metadata = RepositoryMetadataStore.getInstance().getMetadata(pluginId());
for (ConfigurationProperty configurationProperty : packageRepository.getConfiguration()) {
handleProperty(list, metadata, configurationProperty);
}
}
private void handleProperty(List<String> list, PackageConfigurations metadata, ConfigurationProperty configurationProperty) {
PackageConfiguration packageConfiguration = null;
if (metadata != null) {
packageConfiguration = metadata.get(configurationProperty.getConfigurationKey().getName());
}
if (packageConfiguration == null || packageConfiguration.getOption(PackageConfiguration.PART_OF_IDENTITY)) {
list.add(configurationProperty.forFingerprint());
}
}
public void applyPackagePluginMetadata(String pluginId) {
for (ConfigurationProperty configurationProperty : configuration) {
PackageMetadataStore packageMetadataStore = PackageMetadataStore.getInstance();
if (packageMetadataStore.getMetadata(pluginId) != null) {
boolean isSecureProperty = packageMetadataStore.hasOption(pluginId, configurationProperty.getConfigurationKey().getName(), PackageConfiguration.SECURE);
configurationProperty.handleSecureValueConfiguration(isSecureProperty);
}
}
}
public void setConfigAttributes(Object attributes) {
if (attributes == null) {
return;
}
Map map = (Map) attributes;
name = (String) map.get("name");
if (map.containsKey(Configuration.CONFIGURATION) && packageRepository != null) {
configuration.setConfigAttributes(map.get(Configuration.CONFIGURATION), getSecureKeyInfoProvider());
}
}
private SecureKeyInfoProvider getSecureKeyInfoProvider() {
PackageMetadataStore packageMetadataStore = PackageMetadataStore.getInstance();
final PackageConfigurations metadata = packageMetadataStore.getMetadata(pluginId());
if (metadata == null) {
return null;
}
return new SecureKeyInfoProvider() {
@Override
public boolean isSecure(String key) {
PackageConfiguration packageConfiguration = metadata.get(key);
return packageConfiguration.getOption(PackageConfiguration.SECURE);
}
};
}
public void addConfigurationErrorFor(String key, String message) {
configuration.addErrorFor(key, message);
}
@Override
public String toString() {
return "PackageDefinition{" +
"configuration=" + configuration +
", id='" + id + '\'' +
", name='" + name + '\'' +
'}';
}
public void clearEmptyConfigurations() {
configuration.clearEmptyConfigurations();
}
public void validateNameUniqueness(HashMap<String, PackageDefinition> nameMap) {
String errorMessageForDuplicateName = String.format("You have defined multiple packages called '%s'. Package names are case-insensitive and must be unique within a repository.", name);
PackageDefinition repoWithSameFieldValue = nameMap.get(name.toLowerCase());
if (repoWithSameFieldValue == null) {
nameMap.put(name.toLowerCase(), this);
} else {
addError(NAME, errorMessageForDuplicateName);
}
}
@PostConstruct
public void ensureIdExists() {
if (StringUtil.isBlank(getId())) {
setId(UUID.randomUUID().toString());
}
}
}