/*
* Copyright 2015 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.scm.SCM;
import com.thoughtworks.go.plugin.access.scm.SCMMetadataStore;
import com.thoughtworks.go.util.FileUtil;
import com.thoughtworks.go.util.StringUtil;
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;
@ConfigTag(value = "scm")
public class PluggableSCMMaterialConfig extends AbstractMaterialConfig {
public static final String TYPE = "PluggableSCMMaterial";
public static final String SCM_ID = "scmId";
public static final String FOLDER = "folder";
public static final String FILTER = "filterAsString";
@ConfigAttribute(value = "ref")
private String scmId;
@IgnoreTraversal
@ConfigReferenceElement(referenceAttribute = "ref", referenceCollection = "scms")
private SCM scmConfig;
@ConfigAttribute(value = "dest", allowNull = true)
protected String folder;
@ConfigSubtag
private Filter filter;
public PluggableSCMMaterialConfig() {
super(TYPE);
}
public PluggableSCMMaterialConfig(String scmId) {
this();
this.scmId = scmId;
}
public PluggableSCMMaterialConfig(CaseInsensitiveString name, SCM scmConfig, String folder, Filter filter) {
super(TYPE);
this.name = name;
this.scmId = scmConfig == null ? null : scmConfig.getSCMId();
this.scmConfig = scmConfig;
this.folder = folder;
this.filter = filter;
}
public String getScmId() {
return scmId;
}
public void setScmId(String scmId) {
this.scmId = scmId;
}
public SCM getSCMConfig() {
return scmConfig;
}
public void setSCMConfig(SCM scmConfig) {
this.scmConfig = scmConfig;
this.scmId = scmConfig == null ? null : scmConfig.getId();
}
@Override
public String getFolder() {
return folder;
}
public File workingdir(File baseFolder) {
if (getFolder() == null) {
return baseFolder;
}
return new File(baseFolder, getFolder());
}
public void setFolder(String folder) {
this.folder = folder;
}
@Override
public Filter filter() {
if (filter == null) {
return new Filter();
}
return filter;
}
public String getFilterAsString() {
return filter().getStringForDisplay();
}
@Override
public boolean isInvertFilter() {
return false;
}
// most of the material such as git, hg, p4 all print the file from the root without '/'. but svn print it with '/', we standardize it here.
@Override
public boolean matches(String name, String regex) {
if (regex.startsWith("/")) {
regex = regex.substring(1);
}
return name.matches(regex);
}
public void setFilter(Filter filter) {
this.filter = filter;
}
public String getPluginId() {
return scmConfig.getPluginConfiguration().getId();
}
@Override
public String getFingerprint() {
if (scmConfig == null) {
return null;
}
return scmConfig.getFingerprint();
}
@Override
protected void appendCriteria(Map<String, Object> parameters) {
parameters.put("fingerprint", getFingerprint());
}
@Override
protected void appendAttributes(Map<String, Object> parameters) {
parameters.put("scmName", scmConfig.getName());
}
@Override
protected void appendPipelineUniqueCriteria(Map<String, Object> basicCriteria) {
basicCriteria.put("dest", folder);
}
@Override
public String getTypeForDisplay() {
String type = scmConfig == null ? null : SCMMetadataStore.getInstance().displayValue(scmConfig.getPluginConfiguration().getId());
return type == null ? "SCM" : type;
}
@Override
public boolean isAutoUpdate() {
return scmConfig.isAutoUpdate();
}
@Override
public void setAutoUpdate(boolean autoUpdate) {
scmConfig.setAutoUpdate(autoUpdate);
}
@Override
public Boolean isUsedInFetchArtifact(PipelineConfig pipelineConfig) {
return Boolean.FALSE;
}
public void setConfigAttributes(Object attributes) {
if (attributes == null) {
return;
}
super.setConfigAttributes(attributes);
Map map = (Map) attributes;
this.scmId = (String) map.get(SCM_ID);
if (map.containsKey(FOLDER)) {
String folder = (String) map.get(FOLDER);
if (StringUtils.isBlank(folder)) {
folder = null;
}
this.folder = folder;
}
if (map.containsKey(FILTER)) {
String pattern = (String) map.get(FILTER);
if (!StringUtil.isBlank(pattern)) {
this.setFilter(Filter.fromDisplayString(pattern));
} else {
this.setFilter(null);
}
}
}
private boolean nameIsEmpty() {
return (name == null || name.isBlank());
}
private boolean scmNameIsEmpty() {
return (scmConfig == null || scmConfig.getName() == null || scmConfig.getName().isEmpty());
}
@Override
public CaseInsensitiveString getName() {
if (nameIsEmpty() && !scmNameIsEmpty()) {
return new CaseInsensitiveString(scmConfig.getName());
} else {
return name;
}
}
@Override
public String getDisplayName() {
CaseInsensitiveString name = getName();
return name == null || name.isBlank() ? getUriForDisplay() : CaseInsensitiveString.str(name);
}
@Override
public String getDescription() {
return getDisplayName();
}
@Override
public String getLongDescription() {
return getUriForDisplay();
}
@Override
public String getUriForDisplay() {
return scmConfig.getConfigForDisplay();
}
@Override
protected void validateConcreteMaterial(ValidationContext validationContext) {
validateDestFolderPath();
validateNotOutsideSandbox();
validateScmID(validationContext);
}
private void validateScmID(ValidationContext validationContext) {
if (StringUtils.isBlank(scmId)) {
addError(SCM_ID, "Please select a SCM");
}
}
@Override
protected void validateExtras(ValidationContext validationContext) {
if (!StringUtil.isBlank(scmId)) {
SCM scm = validationContext.findScmById(scmId);
if (scm == null) {
addError(SCM_ID, String.format("Could not find SCM for given scm-id: [%s].", scmId));
} else if (!scm.doesPluginExist()) {
addError(SCM_ID, String.format("Could not find plugin for scm-id: [%s].", scmId));
}
}
}
private void validateDestFolderPath() {
if (StringUtils.isBlank(folder)) {
return;
}
if (!new FilePathTypeValidator().isPathValid(folder)) {
addError(FOLDER, FilePathTypeValidator.errorMessage("directory", getFolder()));
}
}
private void validateNotOutsideSandbox() {
String dest = this.getFolder();
if (dest == null) {
return;
}
if (!(FileUtil.isFolderInsideSandbox(dest))) {
addError(FOLDER, String.format("Dest folder '%s' is not valid. It must be a sub-directory of the working folder.", dest));
}
}
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.");
}
}
@Override
public void validateNameUniqueness(Map<CaseInsensitiveString, AbstractMaterialConfig> map) {
if (StringUtils.isBlank(scmId)) {
return;
}
if (map.containsKey(new CaseInsensitiveString(scmId))) {
AbstractMaterialConfig material = map.get(new CaseInsensitiveString(scmId));
material.addError(SCM_ID, "Duplicate SCM material detected!");
addError(SCM_ID, "Duplicate SCM material detected!");
} else {
map.put(new CaseInsensitiveString(scmId), this);
}
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
PluggableSCMMaterialConfig that = (PluggableSCMMaterialConfig) o;
if (folder != null ? !folder.equals(that.folder) : that.folder != null) {
return false;
}
if (scmConfig != null ? !scmConfig.equals(that.scmConfig) : that.scmConfig != null) {
return false;
}
return super.equals(that);
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + (scmId != null ? scmId.hashCode() : 0);
result = 31 * result + (folder != null ? folder.hashCode() : 0);
return result;
}
@Override
public String toString() {
return String.format("'PluggableSCMMaterial{%s}'", getLongDescription());
}
}