/* * Copyright 2017 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.dao; import com.ibatis.sqlmap.client.SqlMapClient; import com.rits.cloning.Cloner; import com.thoughtworks.go.config.GoConfigDao; import com.thoughtworks.go.database.Database; import com.thoughtworks.go.domain.Pipeline; import com.thoughtworks.go.domain.PipelineState; import com.thoughtworks.go.domain.Stage; import com.thoughtworks.go.domain.StageIdentifier; import com.thoughtworks.go.server.cache.GoCache; import com.thoughtworks.go.server.domain.StageStatusListener; import com.thoughtworks.go.server.persistence.MaterialRepository; import com.thoughtworks.go.server.transaction.SqlMapClientDaoSupport; import com.thoughtworks.go.server.transaction.TransactionSynchronizationManager; import com.thoughtworks.go.server.transaction.TransactionTemplate; import com.thoughtworks.go.util.SystemEnvironment; import org.apache.log4j.Logger; import org.hibernate.Criteria; import org.hibernate.SessionFactory; import org.hibernate.criterion.Projections; import org.hibernate.criterion.PropertyProjection; import org.hibernate.criterion.Restrictions; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallback; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionSynchronizationAdapter; import java.util.List; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; @SuppressWarnings({"ALL"}) @Component public class PipelineStateDao extends SqlMapClientDaoSupport implements StageStatusListener { private static final Logger LOGGER = Logger.getLogger(PipelineStateDao.class); private StageDao stageDao; private MaterialRepository materialRepository; private EnvironmentVariableDao environmentVariableDao; private GoCache goCache; private TransactionTemplate transactionTemplate; private TransactionSynchronizationManager transactionSynchronizationManager; private final SystemEnvironment systemEnvironment; private final GoConfigDao configFileDao; private SessionFactory sessionFactory; private final Cloner cloner = new Cloner(); private final ReadWriteLock activePipelineRWLock = new ReentrantReadWriteLock(); private final Lock activePipelineReadLock = activePipelineRWLock.readLock(); private final Lock activePipelineWriteLock = activePipelineRWLock.writeLock(); @Autowired public PipelineStateDao(StageDao stageDao, MaterialRepository materialRepository, GoCache goCache, EnvironmentVariableDao environmentVariableDao, TransactionTemplate transactionTemplate, SqlMapClient sqlMapClient, TransactionSynchronizationManager transactionSynchronizationManager, SystemEnvironment systemEnvironment, GoConfigDao configFileDao, Database database, SessionFactory sessionFactory) { super(goCache, sqlMapClient, systemEnvironment, database); this.stageDao = stageDao; this.materialRepository = materialRepository; this.goCache = goCache; this.environmentVariableDao = environmentVariableDao; this.transactionTemplate = transactionTemplate; this.transactionSynchronizationManager = transactionSynchronizationManager; this.systemEnvironment = systemEnvironment; this.configFileDao = configFileDao; this.sessionFactory = sessionFactory; } public void stageStatusChanged(Stage stage) { clearLockedPipelineStateCache(stage.getIdentifier().getPipelineName()); } public void lockPipeline(final Pipeline pipeline) { synchronized (pipelineLockStateCacheKey(pipeline.getName())) { transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { transactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() { @Override public void afterCommit() { clearLockedPipelineStateCache(pipeline.getName()); } }); final String pipelineName = pipeline.getName(); PipelineState fromCache = pipelineStateFor(pipelineName); if (fromCache != null && fromCache.isLocked() && !pipeline.getIdentifier().equals(fromCache.getLockedBy().pipelineIdentifier())) { throw new RuntimeException(String.format("Pipeline '%s' is already locked (counter = %s)", pipelineName, fromCache.getLockedBy().getPipelineCounter())); } PipelineState toBeSaved = null; if (fromCache == null) { toBeSaved = new PipelineState(pipelineName); } else { toBeSaved = (PipelineState) sessionFactory.getCurrentSession().load(PipelineState.class, fromCache.getId()); } toBeSaved.lock(pipeline.getId()); sessionFactory.getCurrentSession().saveOrUpdate(toBeSaved); } }); } } private void clearLockedPipelineStateCache(String pipelineName) { goCache.remove(pipelineLockStateCacheKey(pipelineName)); } public void unlockPipeline(final String pipelineName) { synchronized (pipelineLockStateCacheKey(pipelineName)) { transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { transactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() { @Override public void afterCommit() { clearLockedPipelineStateCache(pipelineName); } }); final String cacheKey = pipelineLockStateCacheKey(pipelineName); PipelineState fromCache = pipelineStateFor(pipelineName); PipelineState toBeSaved = null; if (fromCache == null) { toBeSaved = new PipelineState(pipelineName); } else { toBeSaved = (PipelineState) sessionFactory.getCurrentSession().load(PipelineState.class, fromCache.getId()); } toBeSaved.unlock(); sessionFactory.getCurrentSession().saveOrUpdate(toBeSaved); } }); } } public PipelineState pipelineStateFor(String pipelineName) { String cacheKey = pipelineLockStateCacheKey(pipelineName); PipelineState pipelineState = (PipelineState) goCache.get(cacheKey); if (pipelineState != null) { return pipelineState.equals(PipelineState.NOT_LOCKED) ? null : pipelineState; } synchronized (cacheKey) { pipelineState = (PipelineState) goCache.get(cacheKey); if (pipelineState != null) { return pipelineState.equals(PipelineState.NOT_LOCKED) ? null : pipelineState; } pipelineState = (PipelineState) transactionTemplate.execute(new TransactionCallback() { @Override public Object doInTransaction(TransactionStatus transactionStatus) { return sessionFactory.getCurrentSession() .createCriteria(PipelineState.class) .add(Restrictions.eq("pipelineName", pipelineName)) .setCacheable(false).uniqueResult(); } }); if (pipelineState != null && pipelineState.isLocked()) { StageIdentifier lockedBy = (StageIdentifier) getSqlMapClientTemplate().queryForObject("lockedPipeline", pipelineState.getLockedByPipelineId()); pipelineState.setLockedBy(lockedBy); } goCache.put(cacheKey, pipelineState == null ? PipelineState.NOT_LOCKED : pipelineState); return pipelineState; } } String pipelineLockStateCacheKey(String pipelineName) { // we intern() it because we synchronize on the returned String return (PipelineStateDao.class.getName() + "_lockedPipeline_" + pipelineName.toLowerCase()).intern(); } public List<String> lockedPipelines() { return (List<String>) transactionTemplate.execute(new TransactionCallback() { @Override public Object doInTransaction(TransactionStatus status) { PropertyProjection pipelineName = Projections.property("pipelineName"); Criteria criteria = sessionFactory.getCurrentSession().createCriteria(PipelineState.class).setProjection(pipelineName).add( Restrictions.eq("locked", true)); criteria.setCacheable(false); List<String> list = criteria.list(); return list; } }); } }