Commit 1398865d authored by Matija Obreza's avatar Matija Obreza

Merge branch '522-number-of-subsets-and-datasets' into 'master'

Resolve "Number of subsets and datasets"

Closes #522

See merge request genesys-pgr/genesys-server!550
parents 7450ce3b 08ce8189
......@@ -17,9 +17,11 @@ package org.genesys.catalog.persistence.dataset;
import java.util.List;
import com.google.common.collect.Lists;
import org.genesys.catalog.model.dataset.Dataset;
import org.genesys.catalog.model.dataset.DatasetAccessionRef;
import org.genesys.catalog.model.dataset.QDatasetAccessionRef;
import org.genesys2.server.model.genesys.AccessionId;
import org.genesys2.server.persistence.AccessionRefRepositoryCustomImpl;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
......@@ -47,4 +49,12 @@ public class DatasetAccessionRefRepositoryCustomImpl extends AccessionRefReposit
return new PageImpl<>(l, page, total);
}
@Override
public List<AccessionId> findAccessionIdsByList(Dataset list) {
JPAQuery<AccessionId> q = jpaQueryFactory.select(QDatasetAccessionRef.datasetAccessionRef.accession.accessionId)
.from(QDatasetAccessionRef.datasetAccessionRef)
.where(QDatasetAccessionRef.datasetAccessionRef.list.eq(list));
return Lists.newArrayList(q.fetch());
}
}
/*
* Copyright 2020 Global Crop Diversity Trust
*
* 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 org.genesys2.server.component.aspect;
import javax.annotation.Resource;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import com.google.common.collect.Lists;
import org.apache.commons.collections4.CollectionUtils;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.genesys.catalog.model.dataset.Dataset;
import org.genesys.catalog.persistence.dataset.DatasetAccessionRefRepository;
import org.genesys2.server.component.elastic.ElasticReindex;
import org.genesys2.server.model.PublishState;
import org.genesys2.server.model.genesys.Accession;
import org.genesys2.server.model.genesys.AccessionId;
import org.genesys2.server.model.impl.Subset;
import org.genesys2.server.persistence.AccessionIdRepository;
import org.genesys2.server.persistence.SubsetAccessionRefRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
/**
* This aspect updates {@link AccessionId#datasetCount} and {@link AccessionId#subsetCount}.
* - when associated subset/dataset is published, the count is increased by 1;
* - when associated subset/dataset is unpublished, the count is decreased by 1.
*
* @author Maxym Borodenko
*/
@Aspect
@Component
public class DatasetAndSubsetCountAspect {
/** The Constant LOG. */
private final static Logger LOG = LoggerFactory.getLogger(DatasetAndSubsetCountAspect.class);
@Autowired
private AccessionIdRepository accessionIdRepository;
@Autowired
private DatasetAccessionRefRepository datasetAccessionRefRepository;
@Autowired
private SubsetAccessionRefRepository subsetAccessionRefRepository;
@Autowired
private ThreadPoolTaskExecutor taskExecutor;
@Resource
private BlockingQueue<ElasticReindex> elasticReindexQueue;
@AfterReturning(value = "execution(* org.genesys.catalog.service.*.approveDataset(..)) || execution(* org.genesys.catalog.service.*.rejectDataset(..))", returning = "result")
public void afterDatasetPersist(final Dataset result) {
if (result == null || result.getAccessionCount() == 0) {
return;
}
List<AccessionId> accessionIds = datasetAccessionRefRepository.findAccessionIdsByList(result);
Lists.partition(accessionIds, 1000).parallelStream().forEach(batch -> {
try {
if (result.getState().equals(PublishState.PUBLISHED)) {
accessionIdRepository.increaseDatasetCount(batch);
} else {
accessionIdRepository.decreaseDatasetCount(batch);
}
scheduleReindexing(batch);
} catch (Throwable ex) {
LOG.warn("Error updating datasetCount of accessions, error is {}", ex.getMessage());
}
});
}
@AfterReturning(value = "execution(* org.genesys2.server.service.*.approveSubset(..)) || execution(* org.genesys2.server.service.*.rejectSubset(..))", returning = "result")
public void afterSubsetPersist(final Subset result) {
if (result == null || result.getAccessionCount() == 0) {
return;
}
List<AccessionId> accessionIds = subsetAccessionRefRepository.findAccessionIdsByList(result);
Lists.partition(accessionIds, 1000).parallelStream().forEach(batch -> {
try {
if (result.getState().equals(PublishState.PUBLISHED)) {
accessionIdRepository.increaseSubsetCount(batch);
} else {
accessionIdRepository.decreaseSubsetCount(batch);
}
scheduleReindexing(batch);
} catch (Throwable ex) {
LOG.warn("Error updating datasetCount of accessions, error is {}", ex.getMessage());
}
});
}
private void scheduleReindexing(List<AccessionId> toReindex) {
if (CollectionUtils.isEmpty(toReindex)) {
return;
}
taskExecutor.execute(() -> toReindex.forEach(accessionId -> {
LOG.trace("Scheduling reindexing of {} {}", Accession.class.getName(), accessionId.getId());
elasticReindexQueue.add(new ElasticReindex(Accession.class.getName(), accessionId.getId()));
}));
}
}
......@@ -133,8 +133,7 @@ public class GenesysImageGalleryAspects extends AbstractImageGalleryAspects impl
ImageGallery imageGallery = instituteFilesService.createImageGallery(accession.getInstitute(), accession);
if (accessionId.getRepositoryFolder() == null) {
accessionId.setRepositoryFolder(imageGallery.getFolder());
accessionIdRepository.save(accessionId);
accessionIdRepository.updateRepositoryFolder(accessionId, imageGallery.getFolder());
}
}
}
......@@ -168,11 +167,10 @@ public class GenesysImageGalleryAspects extends AbstractImageGalleryAspects impl
try {
ImageGallery imageGallery = instituteFilesService.loadImageGallery(accession.getInstitute(), accession);
if (imageGallery != null) {
accessionId.setImageCount(imageGallery.getImages().size());
accessionIdRepository.updateImageCount(accessionId, imageGallery.getImages().size());
} else {
accessionId.setImageCount(0);
accessionIdRepository.updateImageCount(accessionId, 0);
}
accessionIdRepository.save(accessionId);
} catch (InvalidRepositoryPathException e) {
LOG.warn("Error updating imageCount of accession, error is {}", e.getMessage());
}
......
......@@ -162,6 +162,12 @@ public class AccessionId extends AuditedVersionedModel implements IdUUID {
@ColumnDefault("0")
private int imageCount;
/** Number of associated published subsets. */
private long subsetCount = 0;
/** Number of associated published datasets. */
private long datasetCount = 0;
@OneToOne(fetch = FetchType.LAZY, cascade = { CascadeType.REMOVE }, optional = true, orphanRemoval = true)
@JoinColumn(name = "folderId", unique = true)
@JsonIgnore
......@@ -315,6 +321,22 @@ public class AccessionId extends AuditedVersionedModel implements IdUUID {
this.imageCount = imageCount;
}
public long getSubsetCount() {
return subsetCount;
}
public void setSubsetCount(long subsetCount) {
this.subsetCount = subsetCount;
}
public long getDatasetCount() {
return datasetCount;
}
public void setDatasetCount(long datasetCount) {
this.datasetCount = datasetCount;
}
public RepositoryFolder getRepositoryFolder() {
return repositoryFolder;
}
......
/*
* Copyright 2018 Global Crop Diversity Trust
* Copyright 2020 Global Crop Diversity Trust
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -58,6 +58,7 @@ import org.genesys2.server.persistence.FaoInstituteRepository;
import org.genesys2.server.persistence.PDCIRepository;
import org.genesys2.server.persistence.SubsetRepository;
import org.genesys2.server.persistence.kpi.ExecutionRepository;
import org.genesys2.server.service.AccessionService;
import org.genesys2.server.service.ContentService;
import org.genesys2.server.service.CountryNamesUpdater;
import org.genesys2.server.service.CropService;
......@@ -192,6 +193,9 @@ public class AdminController {
@Autowired
private ScheduledGLISUpdater scheduledGLISUpdater;
@Autowired
private AccessionService accessionService;
@RequestMapping("/")
public String root(Model model) {
return "/admin/index";
......@@ -235,6 +239,24 @@ public class AdminController {
return "redirect:/admin/";
}
@RequestMapping(method = RequestMethod.POST, value = "/scanForSubsets")
public String scanForSubsets() {
accessionService.scanForPublishedSubsets();
return "redirect:/admin/";
}
@RequestMapping(method = RequestMethod.POST, value = "/scanForDatasets")
public String scanForDatasets() {
accessionService.scanForPublishedDatasets();
return "redirect:/admin/";
}
@RequestMapping(method = RequestMethod.POST, value = "/resetCounters")
public String resetCounters() {
accessionService.resetSubsetAndDatasetCounters();
return "redirect:/admin/";
}
@RequestMapping(method = RequestMethod.POST, value = "/relinkDatasetAccessions")
public String rematchDatasetAccessions() {
datasetService.rematchDatasetAccessions();
......
/**
* Copyright 2014 Global Crop Diversity Trust
/*
* Copyright 2020 Global Crop Diversity Trust
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -12,7 +12,7 @@
* 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 org.genesys2.server.persistence;
......@@ -23,8 +23,9 @@ import org.genesys2.server.model.genesys.AccessionId;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
public interface AccessionIdRepository extends JpaRepository<AccessionId, Long> {
public interface AccessionIdRepository extends JpaRepository<AccessionId, Long>, AccessionIdRepositoryCustom, QuerydslPredicateExecutor<AccessionId> {
@Query("select aid from AccessionId aid where aid.uuid is null")
List<AccessionId> findMissingUuid(Pageable pageable);
......
/*
* Copyright 2020 Global Crop Diversity Trust
*
* 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 org.genesys2.server.persistence;
import org.genesys.filerepository.model.RepositoryFolder;
import org.genesys2.server.model.genesys.AccessionId;
import java.util.List;
/**
* Custom {@link AccessionId} repository methods.
*
* @author Maxym Borodenko
*/
public interface AccessionIdRepositoryCustom {
void increaseDatasetCount(List<AccessionId> accessionIds);
void decreaseDatasetCount(List<AccessionId> accessionIds);
void increaseSubsetCount(List<AccessionId> accessionIds);
void decreaseSubsetCount(List<AccessionId> accessionIds);
void resetSubsetAndDatasetCounters();
void updateImageCount(AccessionId accessionId, int imageCount);
void updateRepositoryFolder(AccessionId accessionId, RepositoryFolder repositoryFolder);
}
/*
* Copyright 2020 Global Crop Diversity Trust
*
* 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 org.genesys2.server.persistence;
import java.util.List;
import com.querydsl.core.types.dsl.NumberPath;
import com.querydsl.jpa.impl.JPAQueryFactory;
import org.genesys.filerepository.model.RepositoryFolder;
import org.genesys2.server.model.genesys.AccessionId;
import org.genesys2.server.model.genesys.QAccessionId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
/**
* @author Maxym Borodenko
*/
@Repository
public class AccessionIdRepositoryCustomImpl implements AccessionIdRepositoryCustom {
@Autowired
private JPAQueryFactory jpaQueryFactory;
@Override
@Transactional
public void increaseDatasetCount(List<AccessionId> accessionIds) {
increaseCount(accessionIds, QAccessionId.accessionId.datasetCount);
}
@Override
@Transactional
public void decreaseDatasetCount(List<AccessionId> accessionIds) {
decreaseCount(accessionIds, QAccessionId.accessionId.datasetCount);
}
@Override
@Transactional
public void increaseSubsetCount(List<AccessionId> accessionIds) {
increaseCount(accessionIds, QAccessionId.accessionId.subsetCount);
}
@Override
@Transactional
public void decreaseSubsetCount(List<AccessionId> accessionIds) {
decreaseCount(accessionIds, QAccessionId.accessionId.subsetCount);
}
@Override
public void resetSubsetAndDatasetCounters() {
NumberPath<Long> subsetCount = QAccessionId.accessionId.subsetCount;
NumberPath<Long> datasetCount = QAccessionId.accessionId.datasetCount;
jpaQueryFactory.update(QAccessionId.accessionId)
.where(datasetCount.gt(0).or(subsetCount.gt(0)))
.set(datasetCount, 0L).set(subsetCount, 0L)
.execute();
}
@Override
public void updateImageCount(AccessionId accessionId, int imageCount) {
jpaQueryFactory.update(QAccessionId.accessionId)
.where(QAccessionId.accessionId.eq(accessionId))
.set(QAccessionId.accessionId.imageCount, Math.max(imageCount, 0))
.execute();
}
@Override
public void updateRepositoryFolder(AccessionId accessionId, RepositoryFolder repositoryFolder) {
jpaQueryFactory.update(QAccessionId.accessionId)
.where(QAccessionId.accessionId.eq(accessionId))
.set(QAccessionId.accessionId.repositoryFolder, repositoryFolder)
.execute();
}
private void decreaseCount(List<AccessionId> accessionIds, NumberPath<Long> path) {
jpaQueryFactory.update(QAccessionId.accessionId)
.where(QAccessionId.accessionId.in(accessionIds).and(path.gt(0)))
.set(path, path.subtract(1))
.execute();
}
private void increaseCount(List<AccessionId> accessionIds, NumberPath<Long> path) {
jpaQueryFactory.update(QAccessionId.accessionId)
.where(QAccessionId.accessionId.in(accessionIds))
.set(path, path.add(1))
.execute();
}
}
......@@ -17,6 +17,7 @@ package org.genesys2.server.persistence;
import java.util.List;
import org.genesys2.server.model.genesys.AccessionId;
import org.genesys2.server.model.genesys.AccessionRef;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
......@@ -32,4 +33,6 @@ public interface AccessionRefCustomRepository<X, T extends AccessionRef<X>> {
Page<T> findByList(X list, Pageable page);
List<AccessionId> findAccessionIdsByList(X list);
}
......@@ -17,6 +17,8 @@ package org.genesys2.server.persistence;
import java.util.List;
import com.google.common.collect.Lists;
import org.genesys2.server.model.genesys.AccessionId;
import org.genesys2.server.model.impl.QSubsetAccessionRef;
import org.genesys2.server.model.impl.Subset;
import org.genesys2.server.model.impl.SubsetAccessionRef;
......@@ -45,4 +47,13 @@ public class SubsetAccessionRefRepositoryCustomImpl extends AccessionRefReposito
List<SubsetAccessionRef> l = q.orderBy(QSubsetAccessionRef.subsetAccessionRef.accession.seqNo.asc(), QSubsetAccessionRef.subsetAccessionRef.acceNumb.asc()).leftJoin(QSubsetAccessionRef.subsetAccessionRef.accession).fetchJoin().offset(page.getOffset()).limit(page.getPageSize()).fetch();
return new PageImpl<>(l, page, total);
}
@Override
public List<AccessionId> findAccessionIdsByList(Subset list) {
JPAQuery<AccessionId> q = jpaQueryFactory.select(QSubsetAccessionRef.subsetAccessionRef.accession.accessionId)
.from(QSubsetAccessionRef.subsetAccessionRef)
.where(QSubsetAccessionRef.subsetAccessionRef.list.eq(list));
return Lists.newArrayList(q.fetch());
}
}
......@@ -186,7 +186,22 @@ public interface AccessionService {
* @return the accession
*/
Accession findMatchingAccession(RepositoryFolder repositoryFolder);
/**
* Scan for published datasets and increase the counts. Admin - only
*/
void scanForPublishedDatasets();
/**
* Scan for published subsets and increase the counts. Admin - only
*/
void scanForPublishedSubsets();
/**
* Reset subset/dataset counters to 0. Admin - only
*/
void resetSubsetAndDatasetCounters();
/**
* Executes the action on a list of accession in a
* Spring transaction. Spring security context not supported.
......
......@@ -28,14 +28,19 @@ import java.util.UUID;
import java.util.regex.Matcher;
import java.util.stream.Collectors;
import com.querydsl.core.types.dsl.BooleanExpression;
import org.apache.commons.collections.CollectionUtils;
import org.genesys.blocks.auditlog.service.AuditTrailService;
import org.genesys.catalog.model.dataset.Dataset;
import org.genesys.catalog.model.dataset.QDataset;
import org.genesys.catalog.persistence.dataset.DatasetRepository;
import org.genesys.catalog.service.DatasetService;
import org.genesys.filerepository.model.ImageGallery;
import org.genesys.filerepository.model.RepositoryFolder;
import org.genesys.filerepository.service.ImageGalleryService;
import org.genesys2.server.component.aspect.DatasetAndSubsetCountAspect;
import org.genesys2.server.exception.NotFoundElement;
import org.genesys2.server.model.PublishState;
import org.genesys2.server.model.genesys.Accession;
import org.genesys2.server.model.genesys.AccessionData;
import org.genesys2.server.model.genesys.AccessionId;
......@@ -43,8 +48,11 @@ import org.genesys2.server.model.genesys.QAccession;
import org.genesys2.server.model.impl.AccessionIdentifier3;
import org.genesys2.server.model.impl.Country;
import org.genesys2.server.model.impl.FaoInstitute;
import org.genesys2.server.model.impl.QSubset;
import org.genesys2.server.model.impl.Subset;
import org.genesys2.server.persistence.AccessionIdRepository;
import org.genesys2.server.persistence.AccessionRepository;
import org.genesys2.server.persistence.SubsetRepository;
import org.genesys2.server.service.AccessionService;
import org.genesys2.server.service.ElasticsearchService;
import org.genesys2.server.service.FilterConstants;
......@@ -55,6 +63,7 @@ import org.genesys2.server.service.TaxonomyService;
import org.genesys2.server.service.filter.AccessionFilter;
import org.genesys2.server.service.filter.AppliedFiltersConverter;
import org.genesys2.server.service.impl.GenesysFilterServiceImpl.LabelValue;
import org.genesys2.spring.TransactionHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
......@@ -62,6 +71,7 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
......@@ -116,6 +126,21 @@ public class AccessionServiceImpl implements AccessionService {
@Autowired
private GeoService geoService;
@Autowired
private DatasetRepository datasetRepository;
@Autowired
private SubsetRepository subsetRepository;
@Autowired
private AccessionIdRepository accessionIdRepository;
@Autowired
private DatasetAndSubsetCountAspect datasetAndSubsetCountAspect;
@Autowired
private ThreadPoolTaskExecutor taskExecutor;
private static final int LOAD_CHUNK_SIZE = 200;
private <T extends AccessionData> T lazyLoad(T accession) {
......@@ -590,4 +615,35 @@ public class AccessionServiceImpl implements AccessionService {
}
return accession;
}
@Override
@PreAuthorize("hasRole('ADMINISTRATOR')")
public void scanForPublishedDatasets() {
BooleanExpression expression = QDataset.dataset.state.eq(PublishState.PUBLISHED);
datasetRepository.findAll(expression).forEach(dataset -> {
taskExecutor.submit(() -> TransactionHelper.executeInTransaction(false, () -> {
datasetAndSubsetCountAspect.afterDatasetPersist(dataset);
return true;
}));
});
}
@Override
@PreAuthorize("hasRole('ADMINISTRATOR')")
public void scanForPublishedSubsets() {
BooleanExpression expression = QSubset.subset.state.eq(PublishState.PUBLISHED);
subsetRepository.findAll(expression).forEach(subset -> {
taskExecutor.submit(() -> TransactionHelper.executeInTransaction(false, () -> {
datasetAndSubsetCountAspect.afterSubsetPersist(subset);
return true;
}));
});
}
@Override
@Transactional
@PreAuthorize("hasRole('ADMINISTRATOR')")
public void resetSubsetAndDatasetCounters() {
accessionIdRepository.resetSubsetAndDatasetCounters();
}
}
/*
* Copyright 2020 Global Crop Diversity Trust
*
* 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 org.genesys2.spring;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicInteger;
import org.genesys.blocks.util.CurrentApplicationContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
/**
* The TransactionHelper.
*
* @author Matija Obreza
*/
public class TransactionHelper {
public static final Logger LOG = LoggerFactory.getLogger(TransactionHelper.class);
public static <T> T asUser(Authentication authentication, Callable<T> callable) throws Exception {
Authentication prevAuth = SecurityContextHolder.getContext().getAuthentication();
SecurityContextHolder.getContext().setAuthentication(authentication);
try {
return callable.call();
} finally {
SecurityContextHolder.getContext().setAuthentication(prevAuth);
}
}
public static <T> T asCurrentUser(Callable<T> callable) throws Exception {
Authentication prevAuth = SecurityContextHolder.getContext().getAuthentication();
try {
LOG.warn("Executing as {}", prevAuth);
return asUser(prevAuth, callable);
} finally {
SecurityContextHolder.getContext().setAuthentication(prevAuth);
}
}
public static <T> T executeInTransaction(boolean readonly, Callable<T> callable) {
final ThrowableHolder throwableHolder = new ThrowableHolder();
AtomicInteger retries = new AtomicInteger(0);
// do {
if (retries.get() > 0) {