/*
* 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.config.materials;
import com.thoughtworks.go.config.*;
import com.thoughtworks.go.config.validation.FilePathTypeValidator;
import com.thoughtworks.go.domain.ConfigErrors;
import com.thoughtworks.go.domain.materials.MaterialConfig;
import com.thoughtworks.go.util.FileUtil;
import com.thoughtworks.go.util.StringUtil;
import com.thoughtworks.go.util.command.UrlArgument;
import org.apache.commons.lang.StringUtils;
import java.io.File;
import java.io.IOException;
import java.util.Map;
import static com.thoughtworks.go.util.ExceptionUtils.bomb;
/**
* @understands a source control repository and its configuration
*/
public abstract class ScmMaterialConfig extends AbstractMaterialConfig implements ParamsAttributeAware {
public static final String URL = "url";
public static final String USERNAME = "username";
@ConfigSubtag
private Filter filter;
@ConfigAttribute(value = "invertFilter", optional = true)
private boolean invertFilter = false;
@ConfigAttribute(value = "dest", allowNull = true)
protected String folder;
@ConfigAttribute(value = "autoUpdate", optional = true)
private boolean autoUpdate = true;
public static final String PASSWORD = "password";
public static final String ENCRYPTED_PASSWORD = "encryptedPassword";
public static final String PASSWORD_CHANGED = "passwordChanged";
public static final String AUTO_UPDATE = "autoUpdate";
public static final String FOLDER = "folder";
public static final String FILTER = "filterAsString";
public static final String INVERT_FILTER = "invertFilter";
public ScmMaterialConfig(String typeName) {
super(typeName);
}
public ScmMaterialConfig(CaseInsensitiveString name, Filter filter, boolean invertFilter, String folder, boolean autoUpdate, String typeName, ConfigErrors errors) {
super(typeName, name, errors);
this.filter = filter;
this.invertFilter = invertFilter;
this.folder = folder;
this.autoUpdate = autoUpdate;
}
@Override protected void appendPipelineUniqueCriteria(Map<String, Object> basicCriteria) {
basicCriteria.put("dest", folder);
}
public File workingdir(File baseFolder) {
if (getFolder() == null) {
return baseFolder;
}
return new File(baseFolder, getFolder());
}
//most of the material such as hg, git, p4 all print the file from the root without '/'
//but subverion print it with '/', we standarize it here. look at the implementation of subversion as well.
public boolean matches(String name, String regex) {
if (regex.startsWith("/")) {
regex = regex.substring(1);
}
return name.matches(regex);
}
public abstract String getUserName();
public abstract String getPassword();
public abstract String getEncryptedPassword();
public abstract boolean isCheckExternals();
public abstract String getUrl();
public abstract void setUrl(String url);
protected abstract UrlArgument getUrlArgument();
protected abstract String getLocation();
public Filter filter() {
if (filter == null) {
return new Filter();
}
return filter;
}
public String getFilterAsString() {
return filter().getStringForDisplay();
}
public Filter rawFilter() {
return filter;
}
public void setFilter(Filter filter) {
this.filter = filter;
}
public boolean isInvertFilter() {
return invertFilter;
}
public boolean getInvertFilter() {
return invertFilter;
}
public void setInvertFilter(boolean value) {
invertFilter = value;
}
public String getDescription() {
return getUriForDisplay();
}
public String getUriForDisplay() {
return getUrlArgument().forDisplay();
}
public String getFolder() {
return folder;
}
public String getDisplayName() {
return name == null ? getUriForDisplay() : CaseInsensitiveString.str(name);
}
public boolean isAutoUpdate() {
return autoUpdate;
}
public boolean getAutoUpdate() {
return autoUpdate;
}
public void setAutoUpdate(boolean value) {
autoUpdate = value;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
if (!super.equals(o)) {
return false;
}
ScmMaterialConfig that = (ScmMaterialConfig) o;
if (folder != null ? !folder.equals(that.folder) : that.folder != null) {
return false;
}
return super.equals(that);
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + (folder != null ? folder.hashCode() : 0);
return result;
}
@Override
protected final void validateConcreteMaterial(ValidationContext validationContext) {
validateNotOutsideSandbox();
validateDestFolderPath();
validateConcreteScmMaterial();
}
public abstract void validateConcreteScmMaterial();
private void validateDestFolderPath() {
if (StringUtils.isBlank(folder)) {
return;
}
if (!new FilePathTypeValidator().isPathValid(folder)) {
errors().add(FOLDER, FilePathTypeValidator.errorMessage("directory", getFolder()));
}
}
public void setConfigAttributes(Object attributes) {
super.setConfigAttributes(attributes);
Map map = (Map) attributes;
if (map.containsKey(FOLDER)) {
String folder = (String) map.get(FOLDER);
if (StringUtils.isBlank(folder)) {
folder = null;
}
this.folder = folder;
}
this.setAutoUpdate("true".equals(map.get(AUTO_UPDATE)));
this.setInvertFilter("true".equals(map.get(INVERT_FILTER)));
if (map.containsKey(FILTER)) {
String pattern = (String) map.get(FILTER);
if (!StringUtil.isBlank(pattern)) {
this.setFilter(Filter.fromDisplayString(pattern));
} else {
this.setFilter(null);
}
}
}
public boolean isAutoUpdateStateMismatch(MaterialConfigs materialAutoUpdateMap) {
if(materialAutoUpdateMap.size() > 1){
for (MaterialConfig otherMaterial : materialAutoUpdateMap) {
if (otherMaterial.isAutoUpdate() != this.autoUpdate) {
return true;
}
}
}
return false;
}
public void setAutoUpdateMismatchError() {
addError(AUTO_UPDATE, String.format("Material of type %s (%s) is specified more than once in the configuration with different values for the autoUpdate attribute."
+ " All copies of this material must have the same value for this attribute.", getTypeForDisplay(), getDescription()));
}
public void setAutoUpdateMismatchErrorWithConfigRepo() {
addError(AUTO_UPDATE, String.format("Material of type %s (%s) is specified as a configuration repository and pipeline material with disabled autoUpdate."
+ " All copies of this material must have autoUpdate enabled or configuration repository must be removed", getTypeForDisplay(), getDescription()));
}
public void setDestinationFolderError(String message) {
addError(FOLDER, message);
}
public void validateNotSubdirectoryOf(String otherSCMMaterialFolder) {
String myDirPath = this.getFolder();
if (myDirPath == null || otherSCMMaterialFolder == null) {
return;
}
File myDir = new File(myDirPath);
File otherDir = new File(otherSCMMaterialFolder);
try {
if (FileUtil.isSubdirectoryOf(myDir, otherDir)) {
addError(FOLDER, "Invalid Destination Directory. Every material needs a different destination directory and the directories should not be nested.");
}
} catch (IOException e) {
throw bomb("Dest folder specification is not valid. " + e.getMessage());
}
}
public void validateDestinationDirectoryName(String otherSCMMaterialFolder) {
if (folder != null && folder.equalsIgnoreCase(otherSCMMaterialFolder)) {
addError(FOLDER, "The destination directory must be unique across materials.");
}
}
private void validateNotOutsideSandbox() {
String dest = this.getFolder();
if (dest == null) {
return;
}
if (!(FileUtil.isFolderInsideSandbox(dest))) {
setDestinationFolderError(String.format("Dest folder '%s' is not valid. It must be a sub-directory of the working folder.", dest));
}
}
public Boolean isUsedInFetchArtifact(PipelineConfig pipelineConfig) {
return false;
}
// TODO: Consider renaming this to dest since we use that word in the UI & Config
public void setFolder(String folder) {
this.folder = folder;
}
}