/* * 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.server.service; import java.util.ArrayList; import java.util.List; import com.thoughtworks.go.domain.JobIdentifier; import com.thoughtworks.go.domain.JobInstance; import com.thoughtworks.go.domain.JobPropertiesReader; import com.thoughtworks.go.domain.JobState; import com.thoughtworks.go.domain.JobStateTransition; import com.thoughtworks.go.domain.Properties; import com.thoughtworks.go.domain.Property; import java.util.LinkedHashMap; import java.util.Map; import com.thoughtworks.go.server.controller.actions.PropertyAction; import com.thoughtworks.go.server.controller.actions.RestfulAction; import com.thoughtworks.go.server.dao.PropertyDao; import com.thoughtworks.go.server.transaction.TransactionTemplate; import com.thoughtworks.go.util.Csv; import com.thoughtworks.go.util.CsvRow; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import static com.thoughtworks.go.server.controller.actions.CsvAction.csvFound; import static com.thoughtworks.go.server.controller.actions.JsonAction.jsonFound; import static com.thoughtworks.go.server.controller.actions.PropertyAction.propertyContainsInvalidChars; import static com.thoughtworks.go.server.controller.actions.PropertyAction.propertyNameToLarge; import static com.thoughtworks.go.server.controller.actions.PropertyAction.propertyValueToLarge; import static com.thoughtworks.go.util.GoConstants.CRUISE_AGENT; import static com.thoughtworks.go.util.GoConstants.CRUISE_JOB_DURATION; import static com.thoughtworks.go.util.GoConstants.CRUISE_JOB_ID; import static com.thoughtworks.go.util.GoConstants.CRUISE_PIPELINE_COUNTER; import static com.thoughtworks.go.util.GoConstants.CRUISE_PIPELINE_LABEL; import static com.thoughtworks.go.util.GoConstants.CRUISE_RESULT; import static com.thoughtworks.go.util.GoConstants.CRUISE_STAGE_COUNTER; import static com.thoughtworks.go.util.GoConstants.CRUISE_TIMESTAMP; import static com.thoughtworks.go.util.DateUtils.formatISO8601; @Service public class PropertiesService implements JobPropertiesReader { private static final int MAX_PROPERTY_SIZE = 255; private static final String VALID_PROPERTY_REGEX = "[0-9a-zA-Z\\-\\_\\.\\/]"; private PropertyDao propertyDao; private TransactionTemplate transactionTemplate; private final JobResolverService jobResolverService; private GoConfigService goConfigService; @Autowired public PropertiesService(PropertyDao propertyDao, GoConfigService goConfigService, TransactionTemplate transactionTemplate, JobResolverService jobResolverService) { this.propertyDao = propertyDao; this.goConfigService = goConfigService; this.transactionTemplate = transactionTemplate; this.jobResolverService = jobResolverService; } public RestfulAction addProperty(Long id, String propertyName, String value) { if (propertyName.length() > MAX_PROPERTY_SIZE) { return propertyNameToLarge(); } if (value.length() > MAX_PROPERTY_SIZE) { return propertyValueToLarge(); } if (invalidPropertyName(propertyName)) { return propertyContainsInvalidChars(); } try { Property property = new Property(propertyName, value); if (propertyDao.save(id, property)) { return PropertyAction.created(property); } return PropertyAction.alreadySet(propertyName); } catch (RuntimeException e) { return PropertyAction.instanceNotFound(e.getMessage()); } } public List<Properties> loadHistory(String pipelineName, String stageName, String jobName, Long pipelineId, Integer limitCount) { if (limitCount <= 0) { return new ArrayList<>(); } return propertyDao.loadHistory(pipelineName, stageName, jobName, pipelineId, limitCount); } public RestfulAction listPropertiesForJob(JobIdentifier jobIdentifier, String type, String propertyKey) { Properties properties; Long buildId = jobIdentifier.getBuildId(); if (propertyKey != null) { String value = propertyDao.value(buildId, propertyKey); if (value == null) { return PropertyAction.propertyNotFound(propertyKey); } properties = new Properties(new Property(propertyKey, value)); } else { properties = propertyDao.list(jobIdentifier.getBuildId()); } return listPropertiesAs(type, properties, jobIdentifier.getBuildName()); } private RestfulAction listPropertiesAs(String type, Properties properties, String jobName) { type = type == null ? "csv" : type; if ("csv".equalsIgnoreCase(type)) { return asCsv(jobName).listProperties(properties); } else { return asJson().listProperties(properties); } } public void saveCruiseProperties(final JobInstance instance) { transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { savePipelineLabel(instance); savePipelineCounter(instance); saveStageCounter(instance); saveBuildAgent(instance); saveBuildResult(instance); saveBuildDuration(instance); saveBuildTransition(instance); saveBuildBuildId(instance); } }); } private void saveStageCounter(JobInstance job) { propertyDao.save(job.getId(), new Property(CRUISE_STAGE_COUNTER, job.getStageCounter())); } private void savePipelineLabel(JobInstance job) { propertyDao.save(job.getId(), new Property(CRUISE_PIPELINE_LABEL, job.getIdentifier().getPipelineLabel())); } private void savePipelineCounter(JobInstance job) { propertyDao.save(job.getId(), new Property(CRUISE_PIPELINE_COUNTER, String.valueOf(job.getIdentifier().getPipelineCounter()))); } private void saveBuildBuildId(JobInstance instance) { propertyDao.save(instance.getId(), new Property(CRUISE_JOB_ID, String.valueOf(instance.getId()))); } private void saveBuildTransition(JobInstance instance) { for (JobStateTransition transition : instance.getTransitions()) { propertyDao.save(instance.getId(), new Property(getTransitionKey(transition.getCurrentState()), formatISO8601(transition.getStateChangeTime()))); } } static String getTransitionKey(JobState state) { String index = state.ordinal() < 10 ? "0" + state.ordinal() : String.valueOf(state.ordinal()); return CRUISE_TIMESTAMP + index + "_" + state.toLowerCase(); } private void saveBuildDuration(JobInstance instance) { propertyDao.save(instance.getId(), new Property(CRUISE_JOB_DURATION, instance.getCurrentBuildDuration())); } private void saveBuildResult(JobInstance instance) { propertyDao.save(instance.getId(), new Property(CRUISE_RESULT, instance.getResult().toString())); } private void saveBuildAgent(JobInstance instance) { propertyDao.save(instance.getId(), new Property(CRUISE_AGENT, goConfigService.agentByUuid(instance.getAgentUuid()).getHostname())); } public Properties getPropertiesForJob(long id) { return propertyDao.list(id); } public static Csv fromAllPropertiesHistory(List<Properties> jobPropertiesHistory) { Csv csv = new Csv(); for (Properties properties : jobPropertiesHistory) { CsvRow row = csv.newRow(); for (Property property : properties) { row.put(property.getKey(), property.getValue()); } } return csv; } public static Csv fromProperties(Properties properties) { Csv csv = new Csv(); CsvRow row = csv.newRow(); for (Property property : properties) { row.put(property.getKey(), property.getValue()); } return csv; } public Properties getPropertiesForOriginalJob(JobIdentifier oldId) { JobIdentifier jobIdentifier = jobResolverService.actualJobIdentifier(oldId); return getPropertiesForJob(jobIdentifier.getBuildId()); } public interface PropertyLister { RestfulAction listPropertiesHistory(List<Properties> jobPropertiesHistory); RestfulAction listProperties(Properties properties); } public static PropertyLister asJson() { return new JsonPropertyLister(); } public static PropertyLister asCsv(String jobName) { return new CsvPropertyLister(jobName); } private static class JsonPropertyLister implements PropertyLister { public RestfulAction listPropertiesHistory(List<Properties> jobPropertiesHistory) { return propHistoryAsJson(jobPropertiesHistory); } public RestfulAction listProperties(Properties properties) { return jsonFound(properties); } private RestfulAction propHistoryAsJson(List<Properties> jobPropertiesHistory) { List jsonList = new ArrayList(); for (Properties properties : jobPropertiesHistory) { Map<String, Object> jsonMap = new LinkedHashMap<>(); jsonMap.put("properties", properties.toJson()); jsonList.add(jsonMap); } return jsonFound(jsonList); } } private static class CsvPropertyLister implements PropertyLister { private final String jobName; public CsvPropertyLister(String jobName) { this.jobName = jobName; } public RestfulAction listPropertiesHistory(List<Properties> jobProperties) { return csvFound(fromAllPropertiesHistory(jobProperties), jobName); } public RestfulAction listProperties(Properties properties) { return csvFound(fromProperties(properties), jobName); } } public boolean invalidPropertyName(String name) { return name.replaceAll(VALID_PROPERTY_REGEX, "").length() != 0; } }