/*
* RHQ Management Platform
* Copyright (C) 2005-2008 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.rhq.enterprise.server.content;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.quartz.CronExpression;
import org.quartz.SchedulerException;
import org.jboss.ejb3.annotation.TransactionTimeout;
import org.rhq.core.domain.auth.Subject;
import org.rhq.core.domain.authz.Permission;
import org.rhq.core.domain.content.Advisory;
import org.rhq.core.domain.content.ContentSource;
import org.rhq.core.domain.content.ContentSyncStatus;
import org.rhq.core.domain.content.Distribution;
import org.rhq.core.domain.content.PackageVersion;
import org.rhq.core.domain.content.PackageVersionContentSource;
import org.rhq.core.domain.content.Repo;
import org.rhq.core.domain.content.RepoAdvisory;
import org.rhq.core.domain.content.RepoContentSource;
import org.rhq.core.domain.content.RepoDistribution;
import org.rhq.core.domain.content.RepoGroup;
import org.rhq.core.domain.content.RepoGroupType;
import org.rhq.core.domain.content.RepoPackageVersion;
import org.rhq.core.domain.content.RepoRelationship;
import org.rhq.core.domain.content.RepoRelationshipType;
import org.rhq.core.domain.content.RepoRepoRelationship;
import org.rhq.core.domain.content.RepoSyncResults;
import org.rhq.core.domain.content.ResourceRepo;
import org.rhq.core.domain.content.composite.RepoComposite;
import org.rhq.core.domain.content.transfer.SubscribedRepo;
import org.rhq.core.domain.criteria.PackageVersionCriteria;
import org.rhq.core.domain.criteria.RepoCriteria;
import org.rhq.core.domain.resource.Resource;
import org.rhq.core.domain.server.PersistenceUtility;
import org.rhq.core.domain.util.PageControl;
import org.rhq.core.domain.util.PageList;
import org.rhq.core.domain.util.PageOrdering;
import org.rhq.enterprise.server.RHQConstants;
import org.rhq.enterprise.server.auth.SubjectManagerLocal;
import org.rhq.enterprise.server.authz.AuthorizationManagerLocal;
import org.rhq.enterprise.server.authz.PermissionException;
import org.rhq.enterprise.server.authz.RequiredPermission;
import org.rhq.enterprise.server.plugin.pc.content.ContentProviderManager;
import org.rhq.enterprise.server.plugin.pc.content.ContentServerPluginContainer;
import org.rhq.enterprise.server.plugin.pc.content.PackageTypeBehavior;
import org.rhq.enterprise.server.plugin.pc.content.RepoDetails;
import org.rhq.enterprise.server.plugin.pc.content.RepoGroupDetails;
import org.rhq.enterprise.server.plugin.pc.content.RepoImportReport;
import org.rhq.enterprise.server.util.CriteriaQueryGenerator;
import org.rhq.enterprise.server.util.CriteriaQueryRunner;
import org.rhq.enterprise.server.util.QueryUtility;
@Stateless
public class RepoManagerBean implements RepoManagerLocal, RepoManagerRemote {
/**
* Refers to the default repo relationship created at DB setup time to represent a parent/child repo
* relationship.
* <p/>
* This probably isn't the best place to store this, but for now this is the primary usage of this
* relationship type.
*/
private static final String PARENT_RELATIONSHIP_NAME = "parent";
private final Log log = LogFactory.getLog(RepoManagerBean.class);
@PersistenceContext(unitName = RHQConstants.PERSISTENCE_UNIT_NAME)
private EntityManager entityManager;
@EJB
private AuthorizationManagerLocal authzManager;
//@IgnoreDependency
@EJB
private ContentSourceManagerLocal contentSourceManager;
@EJB
private SubjectManagerLocal subjectManager;
@EJB
private RepoManagerLocal repoManager;
public void deleteRepo(Subject subject, int repoId) {
if (!authzManager.canUpdateRepo(subject, repoId)) {
throw new PermissionException("User [" + subject + "] cannot delete repository with id " + repoId);
}
log.info("User [" + subject + "] is deleting repository with id [" + repoId + "]...");
// bulk delete m-2-m mappings to the doomed repo
// get ready for bulk delete by clearing entity manager
entityManager.flush();
entityManager.clear();
entityManager.createNamedQuery(ResourceRepo.DELETE_BY_REPO_ID).setParameter("repoId", repoId).executeUpdate();
entityManager.createNamedQuery(RepoContentSource.DELETE_BY_REPO_ID).setParameter("repoId", repoId)
.executeUpdate();
entityManager.createNamedQuery(RepoPackageVersion.DELETE_BY_REPO_ID).setParameter("repoId", repoId)
.executeUpdate();
Repo repo = entityManager.find(Repo.class, repoId);
if (repo != null) {
entityManager.remove(repo);
log.debug("User [" + subject + "] deleted repository [" + repo + "]");
} else {
log.debug("Repository with id [" + repoId + "] doesn't exist - nothing to delete");
}
// remove any unused, orphaned package versions
contentSourceManager.purgeOrphanedPackageVersions(subjectManager.getOverlord());
}
@RequiredPermission(Permission.MANAGE_REPOSITORIES)
public void deleteRepoGroup(Subject subject, int repoGroupId) {
RepoGroup deleteMe = getRepoGroup(subject, repoGroupId);
entityManager.remove(deleteMe);
}
public boolean deletePackageVersionsFromRepo(Subject subject, int repoId, int[] packageVersionIds) {
if (packageVersionIds == null || packageVersionIds.length == 0) {
return true;
}
if (!authzManager.canUpdateRepo(subject, repoId)) {
throw new PermissionException("User [" + subject + "] cannot update the repo with id " + repoId + " and therefore cannot delete package versions from it.");
}
ArrayList<Integer> ids = new ArrayList<Integer>();
for(int id : packageVersionIds) {
ids.add(id);
}
Query deleteable = entityManager.createNamedQuery(PackageVersion.QUERY_FIND_DELETEABLE_IDS_IN_REPO);
deleteable.setParameter("repoId", repoId);
deleteable.setParameter("packageVersionIds", ids);
@SuppressWarnings("unchecked")
List<Integer> deleteableIds = (List<Integer>) deleteable.getResultList();
if (deleteableIds.isEmpty()) {
return false;
}
Query deleteRepoPackageVersions = entityManager.createNamedQuery(RepoPackageVersion.DELETE_MULTIPLE_WHEN_NO_PROVIDER);
deleteRepoPackageVersions.setParameter("repoId", repoId);
deleteRepoPackageVersions.setParameter("packageVersionIds", deleteableIds);
deleteRepoPackageVersions.executeUpdate();
Query deletePackageVersions = entityManager.createNamedQuery(PackageVersion.DELETE_MULTIPLE_IF_NO_CONTENT_SOURCES_OR_REPOS);
deletePackageVersions.setParameter("packageVersionIds", deleteableIds);
int deleted = deletePackageVersions.executeUpdate();
return deleted == packageVersionIds.length;
}
@SuppressWarnings("unchecked")
public PageList<Repo> findRepos(Subject subject, PageControl pc) {
pc.initDefaultOrderingField("c.name");
Query query = null;
Query countQuery = null;
if (authzManager.hasGlobalPermission(subject, Permission.MANAGE_REPOSITORIES)) {
query = PersistenceUtility.createQueryWithOrderBy(entityManager, Repo.QUERY_FIND_ALL_IMPORTED_REPOS_ADMIN, pc);
countQuery = PersistenceUtility.createCountQuery(entityManager, Repo.QUERY_FIND_ALL_IMPORTED_REPOS_ADMIN);
} else {
query = PersistenceUtility.createQueryWithOrderBy(entityManager, Repo.QUERY_FIND_ALL_IMPORTED_REPOS, pc);
countQuery = PersistenceUtility.createCountQuery(entityManager, Repo.QUERY_FIND_ALL_IMPORTED_REPOS);
query.setParameter("subject", subject);
countQuery.setParameter("subject", subject);
}
List<Repo> results = query.getResultList();
long count = (Long) countQuery.getSingleResult();
return new PageList<Repo>(results, (int) count, pc);
}
public Repo getRepo(Subject subject, int repoId) {
if (!authzManager.canViewRepo(subject, repoId)) {
throw new PermissionException("User [" + subject + "] cannot access the repo with id " + repoId);
}
Repo repo = entityManager.find(Repo.class, repoId);
if ((repo != null) && (repo.getRepoContentSources() != null)) {
// load content sources separately. we can't do this all at once via fetch join because
// on Oracle we use a LOB column on a content source field and you can't DISTINCT on LOBs
repo.getRepoContentSources().size();
}
return repo;
}
@RequiredPermission(Permission.MANAGE_REPOSITORIES)
public RepoGroup getRepoGroup(Subject subject, int repoGroupId) {
RepoGroup repoGroup = entityManager.find(RepoGroup.class, repoGroupId);
return repoGroup;
}
@SuppressWarnings("unchecked")
@RequiredPermission(Permission.MANAGE_REPOSITORIES)
public PageList<ContentSource> findAssociatedContentSources(Subject subject, int repoId, PageControl pc) {
pc.initDefaultOrderingField("cs.id");
Query query = PersistenceUtility.createQueryWithOrderBy(entityManager, ContentSource.QUERY_FIND_BY_REPO_ID, pc);
Query countQuery = PersistenceUtility.createCountQuery(entityManager, ContentSource.QUERY_FIND_BY_REPO_ID);
query.setParameter("id", repoId);
countQuery.setParameter("id", repoId);
List<ContentSource> results = query.getResultList();
long count = (Long) countQuery.getSingleResult();
return new PageList<ContentSource>(results, (int) count, pc);
}
@SuppressWarnings("unchecked")
@RequiredPermission(Permission.MANAGE_INVENTORY)
public PageList<Resource> findSubscribedResources(Subject subject, int repoId, PageControl pc) {
pc.initDefaultOrderingField("rc.resource.id");
if (!authzManager.canViewRepo(subject, repoId)) {
throw new PermissionException("User [" + subject + "] can't access repository with id " + repoId);
}
Query query = PersistenceUtility
.createQueryWithOrderBy(entityManager, Repo.QUERY_FIND_SUBSCRIBER_RESOURCES, pc);
Query countQuery = PersistenceUtility.createCountQuery(entityManager, Repo.QUERY_FIND_SUBSCRIBER_RESOURCES);
query.setParameter("id", repoId);
countQuery.setParameter("id", repoId);
List<Resource> results = query.getResultList();
long count = (Long) countQuery.getSingleResult();
return new PageList<Resource>(results, (int) count, pc);
}
@SuppressWarnings("unchecked")
// current resource subscriptions should be viewing, but perhaps available ones shouldn't
public PageList<RepoComposite> findResourceSubscriptions(Subject subject, int resourceId, PageControl pc) {
if (!authzManager.canViewResource(subject, resourceId)) {
throw new PermissionException("User [" + subject + "] can't view resource with id " + resourceId);
}
pc.initDefaultOrderingField("c.id");
Query query = PersistenceUtility.createQueryWithOrderBy(entityManager,
Repo.QUERY_FIND_REPO_COMPOSITES_BY_RESOURCE_ID, pc);
Query countQuery = entityManager.createNamedQuery(Repo.QUERY_FIND_REPO_COMPOSITES_BY_RESOURCE_ID_COUNT);
query.setParameter("resourceId", resourceId);
countQuery.setParameter("resourceId", resourceId);
List<RepoComposite> results = query.getResultList();
long count = (Long) countQuery.getSingleResult();
return new PageList<RepoComposite>(results, (int) count, pc);
}
public List<SubscribedRepo> findSubscriptions(Subject subject, int resourceId) {
if (!authzManager.canViewResource(subject, resourceId)) {
throw new PermissionException("User [" + subject + "] can't view resource with id " + resourceId);
}
List<SubscribedRepo> list = new ArrayList<SubscribedRepo>();
PageControl pc = new PageControl();
for (RepoComposite repoComposite : findResourceSubscriptions(subject, resourceId, pc)) {
Repo repo = repoComposite.getRepo();
SubscribedRepo summary = new SubscribedRepo(repo.getId(), repo.getName());
list.add(summary);
}
return list;
}
@SuppressWarnings("unchecked")
@RequiredPermission(Permission.MANAGE_INVENTORY)
public PageList<RepoComposite> findAvailableResourceSubscriptions(Subject subject, int resourceId, PageControl pc) {
pc.initDefaultOrderingField("c.id");
Query query = null;
Query countQuery = null;
if (authzManager.hasGlobalPermission(subject, Permission.MANAGE_REPOSITORIES)) {
query = PersistenceUtility.createQueryWithOrderBy(entityManager,
Repo.QUERY_FIND_AVAILABLE_REPO_COMPOSITES_BY_RESOURCE_ID_ADMIN, pc);
countQuery = entityManager
.createNamedQuery(Repo.QUERY_FIND_AVAILABLE_REPO_COMPOSITES_BY_RESOURCE_ID_ADMIN_COUNT);
} else {
query = PersistenceUtility.createQueryWithOrderBy(entityManager,
Repo.QUERY_FIND_AVAILABLE_REPO_COMPOSITES_BY_RESOURCE_ID, pc);
countQuery = entityManager
.createNamedQuery(Repo.QUERY_FIND_AVAILABLE_REPO_COMPOSITES_BY_RESOURCE_ID_COUNT);
query.setParameter("subject", subject);
countQuery.setParameter("subject", subject);
}
query.setParameter("resourceId", resourceId);
countQuery.setParameter("resourceId", resourceId);
List<RepoComposite> results = query.getResultList();
long count = (Long) countQuery.getSingleResult();
return new PageList<RepoComposite>(results, (int) count, pc);
}
@SuppressWarnings("unchecked")
public List<RepoComposite> findResourceSubscriptions(int resourceId) {
Query query = entityManager.createNamedQuery(Repo.QUERY_FIND_REPO_COMPOSITES_BY_RESOURCE_ID);
query.setParameter("resourceId", resourceId);
List<RepoComposite> results = query.getResultList();
return results;
}
@SuppressWarnings("unchecked")
public List<RepoComposite> findAvailableResourceSubscriptions(int resourceId) {
Query query = entityManager.createNamedQuery(Repo.QUERY_FIND_AVAILABLE_REPO_COMPOSITES_BY_RESOURCE_ID_ADMIN);
query.setParameter("resourceId", resourceId);
List<RepoComposite> results = query.getResultList();
return results;
}
@SuppressWarnings("unchecked")
public PageList<PackageVersion> findPackageVersionsInRepo(Subject subject, int repoId, PageControl pc) {
if (!authzManager.canViewRepo(subject, repoId)) {
throw new PermissionException("User [" + subject + "] can't access repo with id " + repoId);
}
pc.initDefaultOrderingField("pv.generalPackage.name, pv.version");
Query query = PersistenceUtility.createQueryWithOrderBy(entityManager,
PackageVersion.QUERY_FIND_BY_REPO_ID_WITH_PACKAGE, pc);
query.setParameter("repoId", repoId);
List<PackageVersion> results = query.getResultList();
long count = getPackageVersionCountFromRepo(subject, null, repoId);
return new PageList<PackageVersion>(results, (int) count, pc);
}
@SuppressWarnings("unchecked")
public PageList<PackageVersion> findPackageVersionsInRepo(Subject subject, int repoId, String filter, PageControl pc) {
if (!authzManager.canViewRepo(subject, repoId)) {
throw new PermissionException("User [" + subject + "] can't access repo with id " + repoId);
}
pc.initDefaultOrderingField("pv.generalPackage.name, pv.version");
Query query = PersistenceUtility.createQueryWithOrderBy(entityManager,
PackageVersion.QUERY_FIND_BY_REPO_ID_WITH_PACKAGE_FILTERED, pc);
query.setParameter("repoId", repoId);
query.setParameter("filter", QueryUtility.formatSearchParameter(filter));
query.setParameter("escapeChar", QueryUtility.getEscapeCharacter());
List<PackageVersion> results = query.getResultList();
long count = getPackageVersionCountFromRepo(subject, filter, repoId);
return new PageList<PackageVersion>(results, (int) count, pc);
}
public PackageVersion getLatestPackageVersion(Subject subject, int packageId, int repoId) {
if (!authzManager.canViewRepo(subject, repoId)) {
throw new PermissionException("User [" + subject + "] cannot access the repo with id " + repoId);
}
Query q = entityManager.createNamedQuery(PackageVersion.QUERY_FIND_BY_PACKAGE_AND_REPO_ID);
q.setParameter("packageId", packageId);
q.setParameter("repoId", repoId);
@SuppressWarnings("unchecked")
List<PackageVersion> results = (List<PackageVersion>) q.getResultList();
if (results.size() == 0) {
return null;
} else if (results.size() == 1) {
return results.get(0);
}
PackageVersion latest = results.get(0);
String packageTypeName = latest.getGeneralPackage().getPackageType().getName();
Comparator<PackageVersion> versionComparator = null;
try {
PackageTypeBehavior behavior = ContentManagerHelper.getPackageTypeBehavior(packageTypeName);
versionComparator = behavior.getPackageVersionComparator(packageTypeName);
} catch (Exception e) {
log.error("Could not get the package type behavior for package type '" + packageTypeName + "'. This should not happen.", e);
}
if (versionComparator == null) {
versionComparator = PackageVersion.DEFAULT_COMPARATOR;
}
Iterator<PackageVersion> it = results.iterator();
it.next(); //skip the first element, we don't have to compare it with itself
while(it.hasNext()) {
PackageVersion current = it.next();
if (versionComparator.compare(latest, current) < 0) {
latest = current;
}
}
return latest;
}
public Repo updateRepo(Subject subject, Repo repo) throws RepoException {
validateFields(repo);
if (!authzManager.hasGlobalPermission(subject, Permission.MANAGE_REPOSITORIES)) {
if (!authzManager.canUpdateRepo(subject, repo.getId())) {
throw new PermissionException("User [" + subject + "] can't update repo with id " + repo.getId());
}
//only the repo manager can update the owner of a repo.
//make sure that's the case.
repo.setOwner(subject);
}
// HHH-2864 - Leave this in until we move to hibernate > 3.2.r14201-2
getRepo(subject, repo.getId());
// should we check non-null repo relationships and warn that we aren't changing them?
log.debug("User [" + subject + "] is updating [" + repo + "]...");
repo = entityManager.merge(repo);
log.debug("User [" + subject + "] updated [" + repo + "].");
try {
ContentServerPluginContainer pc = ContentManagerHelper.getPluginContainer();
pc.unscheduleRepoSyncJob(repo);
pc.scheduleRepoSyncJob(repo);
} catch (Exception e) {
log.warn("Failed to reschedule repository synchronization job for [" + repo + "].", e);
}
return repo;
}
public Repo createRepo(Subject subject, Repo repo) throws RepoException {
validateRepo(repo);
if (!authzManager.hasGlobalPermission(subject, Permission.MANAGE_REPOSITORIES)) {
//only the repo manager can update the owner of a repo.
//make sure that's the case.
repo.setOwner(subject);
}
log.debug("User [" + subject + "] is creating [" + repo + "]...");
entityManager.persist(repo);
log.info("User [" + subject + "] created [" + repo + "].");
// If this repo is imported, schedule the repo sync job.
if (!repo.isCandidate()) {
try {
ContentServerPluginContainer pc = ContentManagerHelper.getPluginContainer();
pc.scheduleRepoSyncJob(repo);
} catch (Exception e) {
log.error("Failed to schedule repository synchronization job for [" + repo + "].", e);
throw new RuntimeException(e);
}
}
return repo; // now has the id set
}
@SuppressWarnings("unchecked")
@RequiredPermission(Permission.MANAGE_REPOSITORIES)
public void deleteCandidatesWithOnlyContentSource(Subject subject, int contentSourceId) {
Query query = entityManager.createNamedQuery(Repo.QUERY_FIND_CANDIDATES_WITH_ONLY_CONTENT_SOURCE);
query.setParameter("contentSourceId", contentSourceId);
List<Repo> repoList = query.getResultList();
for (Repo deleteMe : repoList) {
deleteRepo(subject, deleteMe.getId());
}
}
@RequiredPermission(Permission.MANAGE_REPOSITORIES)
public void processRepoImportReport(Subject subject, RepoImportReport report, int contentSourceId,
StringBuilder result) {
// TODO: The below line was added to simplify things for JON (i.e. patches from JBoss CSP) - remove it if we
// need more flexibility for other use cases. (ips, 03/26/10)
boolean autoImport = (report.getRepoGroups().isEmpty() && report.getRepos().size() == 1);
// Import groups first
List<RepoGroupDetails> repoGroups = report.getRepoGroups();
List<RepoGroupDetails> importedRepoGroups = new ArrayList<RepoGroupDetails>();
for (RepoGroupDetails createMe : repoGroups) {
String name = createMe.getName();
RepoGroup existingGroup = getRepoGroupByName(name);
if (existingGroup == null) {
existingGroup = new RepoGroup(name);
existingGroup.setDescription(createMe.getDescription());
RepoGroupType groupType = getRepoGroupTypeByName(subject, createMe.getTypeName());
existingGroup.setRepoGroupType(groupType);
// Don't let the whole report blow up if one of these fails,
// but be sure to mention it in the report.
try {
createRepoGroup(subject, existingGroup);
importedRepoGroups.add(createMe);
} catch (RepoException e) {
if (e.getType() == RepoException.RepoExceptionType.NAME_ALREADY_EXISTS) {
result.append("Skipping existing repository group [").append(name).append("]").append('\n');
} else {
log.error("Error adding repository group [" + name + "]", e);
result.append("Could not add repository group [").append(name)
.append("]. See log for more information.").append('\n');
}
}
}
}
if (importedRepoGroups.isEmpty()) {
result
.append("There are no new repository groups since the last time this content source was synchronized.\n");
} else {
result.append("Imported the following [").append(importedRepoGroups.size())
.append("] repository group(s): ").append(importedRepoGroups).append('\n');
}
// Hold on to all current candidate repos for the content source. If any were not present in this
// report, remove them from the system (the rationale being, the content source no longer knows
// about them and thus they cannot be imported).
RepoCriteria candidateReposCriteria = new RepoCriteria();
candidateReposCriteria.addFilterContentSourceIds(contentSourceId);
candidateReposCriteria.addFilterCandidate(true);
candidateReposCriteria.clearPaging();//disable paging as the code assumes all the results will be returned.
PageList<Repo> candidatesForThisProvider = findReposByCriteria(subject, candidateReposCriteria);
// Once the groups are in the system, import any repos that were added
List<RepoDetails> repos = report.getRepos();
// First add repos that have no parent. We later add repos with a parent afterwards to prevent
// issues where both the parent and child are specified in this report.
List<RepoDetails> importedRepos = new ArrayList<RepoDetails>();
for (RepoDetails createMe : repos) {
if (createMe.getParentRepoName() == null) {
try {
if (addCandidateRepo(contentSourceId, createMe, autoImport)) {
importedRepos.add(createMe);
}
removeRepoFromList(createMe.getName(), candidatesForThisProvider);
} catch (Exception e) {
if (e instanceof RepoException
&& ((RepoException) e).getType() == RepoException.RepoExceptionType.NAME_ALREADY_EXISTS) {
result.append("Skipping addition of existing repository [").append(createMe.getName())
.append("]").append('\n');
} else {
log.error("Error processing repository [" + createMe + "]", e);
result.append("Could not add repository [").append(createMe.getName())
.append("]. See log for more information.").append('\n');
}
}
}
}
// Take a second pass through the list checking for any repos that were created to be
// a child of another repo.
for (RepoDetails createMe : repos) {
if (createMe.getParentRepoName() != null) {
try {
if (addCandidateRepo(contentSourceId, createMe, autoImport)) {
importedRepos.add(createMe);
}
removeRepoFromList(createMe.getName(), candidatesForThisProvider);
} catch (Exception e) {
log.error("Error processing repository [" + createMe + "]", e);
result.append("Could not add repository [").append(createMe.getName())
.append("]. See log for more information.").append('\n');
}
}
}
if (importedRepos.isEmpty()) {
result.append("There are no new repositories since the last time this content source was synchronized.\n");
} else {
result.append("Imported the following ").append(importedRepos.size()).append(" repository(s): ")
.append(importedRepos).append('\n');
}
// Any repos that haven't been removed from candidatesForThisProvider were not returned in this
// report, so remove them from the database.
if (!candidatesForThisProvider.isEmpty()) {
for (Repo deleteMe : candidatesForThisProvider) {
deleteRepo(subject, deleteMe.getId());
}
result.append("Deleted the following ").append(candidatesForThisProvider.size())
.append(" obsolete repository(s): ").append(candidatesForThisProvider).append('\n');
}
}
@RequiredPermission(Permission.MANAGE_REPOSITORIES)
public void importCandidateRepo(Subject subject, List<Integer> repoIds) throws RepoException {
for (Integer repoId : repoIds) {
Repo repo = entityManager.find(Repo.class, repoId);
if (repo == null) {
throw new RepoException("Unable to find candidate repository with id " + repoId + " for import.");
}
if (!repo.isCandidate()) {
throw new RepoException("Unable to import repository with id " + repoId
+ ", because it is already imported.");
}
repo.setCandidate(false);
}
}
public void removeOwnershipOfSubject(int subjectId) {
Query q = entityManager.createNamedQuery(Repo.QUERY_UPDATE_REMOVE_OWNER_FROM_REPOS_OWNED_BY_SUBJECT);
q.setParameter("ownerId", subjectId);
q.executeUpdate();
}
@RequiredPermission(Permission.MANAGE_REPOSITORIES)
public RepoGroup createRepoGroup(Subject subject, RepoGroup repoGroup) throws RepoException {
validateRepoGroup(repoGroup);
entityManager.persist(repoGroup);
return repoGroup;
}
@SuppressWarnings("unchecked")
public List<Repo> getRepoByName(String name) {
Query query = entityManager.createNamedQuery(Repo.QUERY_FIND_BY_NAME);
query.setParameter("name", name);
List<Repo> results = query.getResultList();
return results;
}
@SuppressWarnings("unchecked")
public RepoGroup getRepoGroupByName(String name) {
Query query = entityManager.createNamedQuery(RepoGroup.QUERY_FIND_BY_NAME);
query.setParameter("name", name);
List<RepoGroup> results = query.getResultList();
if (results.size() > 0) {
return results.get(0);
} else {
return null;
}
}
@SuppressWarnings("unchecked")
public RepoGroupType getRepoGroupTypeByName(Subject subject, String name) {
Query query = entityManager.createNamedQuery(RepoGroupType.QUERY_FIND_BY_NAME);
query.setParameter("name", name);
List<RepoGroupType> results = query.getResultList();
if (results.size() > 0) {
return results.get(0);
} else {
return null;
}
}
@SuppressWarnings("unchecked")
@RequiredPermission(Permission.MANAGE_REPOSITORIES)
public void addContentSourcesToRepo(Subject subject, int repoId, int[] contentSourceIds) throws Exception {
Repo repo = entityManager.find(Repo.class, repoId);
if (repo == null) {
throw new Exception("There is no repo with an ID [" + repoId + "]");
}
repo.setLastModifiedDate(System.currentTimeMillis());
log.debug("User [" + subject + "] is adding content sources to repo [" + repo + "]");
ContentServerPluginContainer pc = ContentManagerHelper.getPluginContainer();
Query q = entityManager.createNamedQuery(PackageVersionContentSource.QUERY_FIND_BY_CONTENT_SOURCE_ID_NO_FETCH);
for (int id : contentSourceIds) {
ContentSource contentSource = entityManager.find(ContentSource.class, id);
if (contentSource == null) {
throw new Exception("There is no content source with id [" + id + "]");
}
Set<ContentSource> alreadyAssociatedContentSources = repo.getContentSources();
// Only add it if it's not already associated with this repo.
if (!alreadyAssociatedContentSources.contains(contentSource)) {
RepoContentSource repoContentSourceMapping = repo.addContentSource(contentSource);
entityManager.persist(repoContentSourceMapping);
}
Set<PackageVersion> alreadyAssociatedPackageVersions = new HashSet<PackageVersion>(
repo.getPackageVersions());
// Automatically associate all of the content source's package versions with this repo,
// but *skip* over the ones that are already linked to this repo from a previous association.
q.setParameter("id", contentSource.getId());
List<PackageVersionContentSource> pvcss = q.getResultList();
for (PackageVersionContentSource pvcs : pvcss) {
PackageVersion packageVersion = pvcs.getPackageVersionContentSourcePK().getPackageVersion();
// Only add it if it's not already associated with this repo.
if (!alreadyAssociatedPackageVersions.contains(packageVersion)) {
RepoPackageVersion mapping = new RepoPackageVersion(repo, packageVersion);
entityManager.persist(mapping);
}
}
entityManager.flush();
entityManager.clear();
}
}
public void simpleAddContentSourcesToRepo(Subject subject, int repoId, int[] contentSourceIds) throws Exception {
Repo repo = entityManager.find(Repo.class, repoId);
if (repo == null) {
throw new Exception("There is no repo with an ID [" + repoId + "]");
}
for (int id : contentSourceIds) {
ContentSource cs = entityManager.find(ContentSource.class, id);
if (cs == null) {
throw new Exception("There is no content source with id [" + id + "]");
}
RepoContentSource ccsmapping = repo.addContentSource(cs);
entityManager.persist(ccsmapping);
}
}
public void addPackageVersionsToRepo(Subject subject, int repoId, int[] packageVersionIds) {
if (!authzManager.canUpdateRepo(subject, repoId)) {
throw new PermissionException("User [" + subject + "] can't update repo with id " + repoId);
}
Repo repo = entityManager.find(Repo.class, repoId);
for (int packageVersionId : packageVersionIds) {
PackageVersion packageVersion = entityManager.find(PackageVersion.class, packageVersionId);
RepoPackageVersion mapping = new RepoPackageVersion(repo, packageVersion);
entityManager.persist(mapping);
}
}
@RequiredPermission(Permission.MANAGE_REPOSITORIES)
public void removeContentSourcesFromRepo(Subject subject, int repoId, int[] contentSourceIds) throws RepoException {
Repo repo = getRepo(subject, repoId);
log.debug("User [" + subject + "] is removing content sources from repository [" + repo + "]");
Set<RepoContentSource> currentSet = repo.getRepoContentSources();
if ((currentSet != null) && (currentSet.size() > 0)) {
Set<RepoContentSource> toBeRemoved = new HashSet<RepoContentSource>();
for (RepoContentSource current : currentSet) {
for (int id : contentSourceIds) {
if (id == current.getRepoContentSourcePK().getContentSource().getId()) {
toBeRemoved.add(current);
break;
}
}
}
for (RepoContentSource doomed : toBeRemoved) {
entityManager.remove(doomed);
}
currentSet.removeAll(toBeRemoved);
}
// note that we specifically do not disassociate package versions from the repo, even if those
// package versions come from the content source that is being removed
}
@SuppressWarnings("unchecked")
public void subscribeResourceToRepos(Subject subject, int resourceId, int[] repoIds) {
if ((repoIds == null) || (repoIds.length == 0)) {
return; // nothing to do
}
// make sure the user has permissions to subscribe this resource
if (!authzManager.hasResourcePermission(subject, Permission.MANAGE_CONTENT, resourceId)) {
throw new PermissionException("[" + subject
+ "] does not have permission to subscribe this resource to repos");
}
// find the resource - abort if it does not exist
Resource resource = entityManager.find(Resource.class, resourceId);
if (resource == null) {
throw new RuntimeException("There is no resource with the ID [" + resourceId + "]");
}
// find all the repos and subscribe the resource to each of them
// note that if the length of the ID array doesn't match, then one of the repos doesn't exist
// and we abort altogether - we do not subscribe to anything unless all repo IDs are valid
Query q = entityManager.createNamedQuery(Repo.QUERY_FIND_BY_IDS);
List<Integer> idList = new ArrayList<Integer>(repoIds.length);
for (Integer id : repoIds) {
idList.add(id);
}
q.setParameter("ids", idList);
List<Repo> repos = q.getResultList();
if (repos.size() != repoIds.length) {
throw new RuntimeException("One or more of the repos do not exist [" + idList + "]->[" + repos + "]");
}
//authz check
for(Repo repo : repos) {
if (!authzManager.canViewRepo(subject, repo.getId())) {
throw new PermissionException("User [" + subject + "] cannot access a repo with id " + repo.getId());
}
}
for (Repo repo : repos) {
ResourceRepo mapping = repo.addResource(resource);
entityManager.persist(mapping);
}
}
@SuppressWarnings("unchecked")
public void unsubscribeResourceFromRepos(Subject subject, int resourceId, int[] repoIds) {
if ((repoIds == null) || (repoIds.length == 0)) {
return; // nothing to do
}
// make sure the user has permissions to unsubscribe this resource
if (!authzManager.hasResourcePermission(subject, Permission.MANAGE_CONTENT, resourceId)) {
throw new PermissionException("[" + subject
+ "] does not have permission to unsubscribe this resource from repositories");
}
// find the resource - abort if it does not exist
Resource resource = entityManager.find(Resource.class, resourceId);
if (resource == null) {
throw new RuntimeException("There is no resource with the ID [" + resourceId + "]");
}
// find all the repos and unsubscribe the resource from each of them
// note that if the length of the ID array doesn't match, then one of the repos doesn't exist
// and we abort altogether - we do not unsubscribe from anything unless all repo IDs are valid
Query q = entityManager.createNamedQuery(Repo.QUERY_FIND_BY_IDS);
List<Integer> idList = new ArrayList<Integer>(repoIds.length);
for (Integer id : repoIds) {
idList.add(id);
}
q.setParameter("ids", idList);
List<Repo> repos = q.getResultList();
if (repos.size() != repoIds.length) {
throw new RuntimeException("One or more of the repos do not exist [" + idList + "]->[" + repos + "]");
}
for (Repo repo : repos) {
ResourceRepo mapping = repo.removeResource(resource);
entityManager.remove(mapping);
}
}
public long getPackageVersionCountFromRepo(Subject subject, String filter, int repoId) {
if (!authzManager.canViewRepo(subject, repoId)) {
throw new PermissionException("User [" + subject + "] can't access repo with id " + repoId);
}
Query countQuery = PersistenceUtility.createCountQuery(entityManager,
PackageVersion.QUERY_FIND_BY_REPO_ID_FILTERED);
countQuery.setParameter("repoId", repoId);
countQuery.setParameter("filter", (filter == null) ? null : ("%" + filter.toUpperCase() + "%"));
return ((Long) countQuery.getSingleResult()).longValue();
}
public long getPackageVersionCountFromRepo(Subject subject, int repoId) {
return getPackageVersionCountFromRepo(subject, null, repoId);
}
@SuppressWarnings("unchecked")
public PageList<Repo> findReposByCriteria(Subject subject, RepoCriteria criteria) {
CriteriaQueryGenerator generator = new CriteriaQueryGenerator(subject, criteria);
;
//TODO this needs the authz applied somehow
CriteriaQueryRunner<Repo> queryRunner = new CriteriaQueryRunner(criteria, generator, entityManager);
return queryRunner.execute();
}
@SuppressWarnings("unchecked")
public PageList<PackageVersion> findPackageVersionsInRepoByCriteria(Subject subject, PackageVersionCriteria criteria) {
Integer repoId = criteria.getFilterRepoId();
if ((null == repoId) || (repoId < 1)) {
throw new IllegalArgumentException("Illegal filterResourceId: " + repoId);
}
if (!authzManager.canViewRepo(subject, repoId)) {
throw new PermissionException("User [" + subject + "] can't access repo with id " + repoId);
}
CriteriaQueryGenerator generator = new CriteriaQueryGenerator(subject, criteria);
;
CriteriaQueryRunner<PackageVersion> queryRunner = new CriteriaQueryRunner(criteria, generator, entityManager);
return queryRunner.execute();
}
@RequiredPermission(Permission.MANAGE_REPOSITORIES)
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void addRepoRelationship(Subject subject, int repoId, int relatedRepoId, String relationshipTypeName) {
Repo repo = entityManager.find(Repo.class, repoId);
Repo relatedRepo = entityManager.find(Repo.class, relatedRepoId);
Query typeQuery = entityManager.createNamedQuery(RepoRelationshipType.QUERY_FIND_BY_NAME);
typeQuery.setParameter("name", relationshipTypeName);
RepoRelationshipType relationshipType = (RepoRelationshipType) typeQuery.getSingleResult();
RepoRelationship repoRelationship = new RepoRelationship();
repoRelationship.setRelatedRepo(relatedRepo);
repoRelationship.setRepoRelationshipType(relationshipType);
repoRelationship.addRepo(repo);
entityManager.persist(repoRelationship);
relatedRepo.addRepoRelationship(repoRelationship);
RepoRepoRelationship repoRepoRelationship = new RepoRepoRelationship(repo, repoRelationship);
entityManager.persist(repoRepoRelationship);
repo.addRepoRelationship(repoRelationship);
}
@Override
public byte[] getPackageVersionBytes(Subject subject, int repoId, int packageVersionId) {
if (!authzManager.canViewRepo(subject, repoId)) {
throw new PermissionException("User [" + subject + "] cannot access a repo with id " + repoId);
}
//check that the provided package version actually belongs to the repo
Repo repo = entityManager.find(Repo.class, repoId);
PackageVersion pv = entityManager.find(PackageVersion.class, packageVersionId);
if (pv == null || !pv.getRepos().contains(repo)) {
throw new IllegalArgumentException("The package version with id " + packageVersionId
+ " does not belong to the repo with id " + repoId + " or does not exist.");
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
contentSourceManager.outputPackageVersionBits(pv, out);
byte[] ret = out.toByteArray();
try {
out.close();
} catch (IOException e) {
//this is not gonna happen with a byte array stream
}
return ret;
}
private void validateFields(Repo repo) throws RepoException {
if (repo.getName() == null || repo.getName().trim().equals("")) {
throw new RepoException("Repo name is required");
}
if (repo.getSyncSchedule() != null) {
try {
CronExpression ce = new CronExpression(repo.getSyncSchedule());
} catch (ParseException e) {
throw new RepoException("Repo sync schedule is not a vaild format.");
}
}
}
private void validateRepo(Repo c) throws RepoException {
this.validateFields(c);
List<Repo> repos = getRepoByName(c.getName());
if (repos.size() != 0) {
RepoException e = new RepoException("There is already a repo with the name of [" + c.getName() + "]");
e.setType(RepoException.RepoExceptionType.NAME_ALREADY_EXISTS);
throw e;
}
}
/**
* Tests the values of the given repo group to ensure creating the group would be a valid operation, including
* ensuring the name is specified and there isn't already an existing group with the same name.
*
* @param repoGroup group to test
* @throws RepoException if the group should not be allowed to be created
*/
private void validateRepoGroup(RepoGroup repoGroup) throws RepoException {
if (repoGroup.getName() == null || repoGroup.getName().trim().equals("")) {
throw new RepoException("Repo group name is required");
}
RepoGroup existingRepoGroup = getRepoGroupByName(repoGroup.getName());
if (existingRepoGroup != null) {
RepoException e = new RepoException("There is already a repo group with the name [" + repoGroup.getName()
+ "]");
e.setType(RepoException.RepoExceptionType.NAME_ALREADY_EXISTS);
throw e;
}
}
/**
* Performs the necessary logic to determine if a candidate repo should be added to the system, adding it
* in the process if it needs to. If the repo already exists in the system, associate it with the specified
* content source.
* <p/>
* Calling this method with a repo that has a parent assumes the parent has already been created. This call
* assumes the repo group has been created as well.
*
* @param contentSourceId identifies the content source that introduced the candidate into the system
* @param createMe describes the candidate to be created
*
* @param autoImport whether or not to import the repo
*
* @throws Exception if there is an error associating the content source with the repo or if the repo
* indicates a parent or repo group that does not exist
*/
private boolean addCandidateRepo(int contentSourceId, RepoDetails createMe, boolean autoImport) throws Exception {
Subject overlord = subjectManager.getOverlord();
String name = createMe.getName();
List<Repo> existingRepos = getRepoByName(name);
if (!existingRepos.isEmpty()) {
// The repo already exists - make sure it is associated with the specified content source.
for (Repo existingRepo : existingRepos) {
addContentSourcesToRepo(overlord, existingRepo.getId(), new int[] { contentSourceId });
}
return false;
}
// The repo doesn't exist yet in the system - create it.
Repo addMe = new Repo(name);
addMe.setCandidate(!autoImport);
addMe.setDescription(createMe.getDescription());
String createMeGroup = createMe.getRepoGroup();
if (createMeGroup != null) {
RepoGroup group = getRepoGroupByName(createMeGroup);
addMe.addRepoGroup(group);
}
// Add the new candidate to the database
addMe = createRepo(overlord, addMe);
// Associate the content source that introduced the candidate with the repo
addContentSourcesToRepo(overlord, addMe.getId(), new int[] { contentSourceId });
// If the repo indicates it has a parent, create that relationship
String parentName = createMe.getParentRepoName();
if (parentName != null) {
List<Repo> parentList = getRepoByName(parentName);
if (parentList.size() == 0) {
String error = "Attempting to create repo [" + name + "] with parent [" + parentName
+ "] but cannot find the parent";
log.error(error);
throw new RepoException(error);
} else {
Repo parent = parentList.get(0);
addRepoRelationship(overlord, addMe.getId(), parent.getId(), PARENT_RELATIONSHIP_NAME);
}
}
return true;
}
private void removeRepoFromList(String repoName, List<Repo> repoList) {
Repo deleteMe = null;
for (Repo checkMe : repoList) {
if (checkMe.getName().equals(repoName)) {
deleteMe = checkMe;
break;
}
}
if (deleteMe != null) {
repoList.remove(deleteMe);
}
}
@RequiredPermission(Permission.MANAGE_REPOSITORIES)
public long getDistributionCountFromRepo(Subject subject, int repoId) {
Query countQuery = PersistenceUtility.createCountQuery(entityManager, RepoDistribution.QUERY_FIND_BY_REPO_ID);
countQuery.setParameter("repoId", repoId);
return (Long) countQuery.getSingleResult();
}
@RequiredPermission(Permission.MANAGE_REPOSITORIES)
@SuppressWarnings("unchecked")
public PageList<Distribution> findAssociatedDistributions(Subject subject, int repoid, PageControl pc) {
pc.setPrimarySort("rkt.id", PageOrdering.ASC);
Query query = PersistenceUtility.createQueryWithOrderBy(entityManager, RepoDistribution.QUERY_FIND_BY_REPO_ID,
pc);
query.setParameter("repoId", repoid);
List<RepoDistribution> results = query.getResultList();
ArrayList<Distribution> distros = new ArrayList();
for (RepoDistribution result : results) {
distros.add(result.getRepoDistributionPK().getDistribution());
}
long count = getDistributionCountFromRepo(subject, repoid);
return new PageList<Distribution>(distros, (int) count, pc);
}
@RequiredPermission(Permission.MANAGE_REPOSITORIES)
@SuppressWarnings("unchecked")
public PageList<Advisory> findAssociatedAdvisory(Subject subject, int repoid, PageControl pc) {
pc.setPrimarySort("rkt.id", PageOrdering.ASC);
Query query = PersistenceUtility.createQueryWithOrderBy(entityManager, RepoAdvisory.QUERY_FIND_BY_REPO_ID, pc);
query.setParameter("repoId", repoid);
List<RepoAdvisory> results = query.getResultList();
ArrayList<Advisory> advs = new ArrayList();
for (RepoAdvisory result : results) {
advs.add(result.getAdvisory());
}
log.debug("list of Advisory : " + advs + " associated to the repo: " + repoid);
long count = getAdvisoryCountFromRepo(subject, repoid);
return new PageList<Advisory>(advs, (int) count, pc);
}
@RequiredPermission(Permission.MANAGE_REPOSITORIES)
public long getAdvisoryCountFromRepo(Subject subject, int repoId) {
Query countQuery = PersistenceUtility.createCountQuery(entityManager, RepoAdvisory.QUERY_FIND_BY_REPO_ID);
countQuery.setParameter("repoId", repoId);
return (Long) countQuery.getSingleResult();
}
public String calculateSyncStatus(Subject subject, int repoId) {
Repo found = this.getRepo(subject, repoId);
List<RepoSyncResults> syncResults = found.getSyncResults();
// Add the most recent sync results status
int latestIndex = syncResults.size() - 1;
if (!syncResults.isEmpty() && syncResults.get(latestIndex) != null) {
RepoSyncResults results = syncResults.get(latestIndex);
return results.getStatus().toString();
} else {
return ContentSyncStatus.NONE.toString();
}
}
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public RepoSyncResults getMostRecentSyncResults(Subject subject, int repoId) {
Repo found = this.getRepo(subject, repoId);
List<RepoSyncResults> syncResults = found.getSyncResults();
int latestIndex = syncResults.size() - 1;
if (syncResults != null && (!syncResults.isEmpty()) && syncResults.get(latestIndex) != null) {
return syncResults.get(latestIndex);
} else {
return null;
}
}
@RequiredPermission(Permission.MANAGE_REPOSITORIES)
public int synchronizeRepos(Subject subject, int[] repoIds) throws Exception {
int syncCount = 0;
ContentServerPluginContainer pc = ContentManagerHelper.getPluginContainer();
for (int id : repoIds) {
try {
Repo repo = getRepo(subject, id);
pc.syncRepoNow(repo);
syncCount++;
} catch (SchedulerException e) {
log.error("Error synchronizing repo with id [" + id + "]", e);
}
}
return syncCount;
}
@TransactionAttribute(TransactionAttributeType.SUPPORTS)
// TEMPORARY TIMEOUT DEFINED WHILE WE PROPERLY FIGURE OUT TIMEOUTS
@TransactionTimeout(86400)
public int internalSynchronizeRepos(Subject subject, Integer[] repoIds) throws InterruptedException {
ContentServerPluginContainer pc;
try {
pc = ContentManagerHelper.getPluginContainer();
} catch (Exception e) {
throw new RuntimeException(e);
}
ContentProviderManager providerManager = pc.getAdapterManager();
int syncCount = 0;
for (Integer id : repoIds) {
boolean syncExecuted = providerManager.synchronizeRepo(id);
if (syncExecuted) {
syncCount++;
}
}
return syncCount;
}
@RequiredPermission(Permission.MANAGE_REPOSITORIES)
public void cancelSync(Subject subject, int repoId) throws ContentException {
ContentServerPluginContainer pc;
try {
pc = ContentManagerHelper.getPluginContainer();
} catch (Exception e) {
throw new RuntimeException(e);
}
Repo repo = this.getRepo(subject, repoId);
try {
pc.cancelRepoSync(subject, repo);
} catch (SchedulerException e) {
throw new ContentException(e);
}
RepoSyncResults results = this.getMostRecentSyncResults(subject, repo.getId());
results.setStatus(ContentSyncStatus.CANCELLING);
repoManager.mergeRepoSyncResults(results);
}
@SuppressWarnings("unchecked")
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public RepoSyncResults persistRepoSyncResults(RepoSyncResults results) {
ContentManagerHelper helper = new ContentManagerHelper(entityManager);
Query q = entityManager.createNamedQuery(RepoSyncResults.QUERY_GET_INPROGRESS_BY_REPO_ID);
q.setParameter("repoId", results.getRepo().getId());
RepoSyncResults persistedSyncResults = (RepoSyncResults) helper.persistSyncResults(q, results);
return (null != persistedSyncResults) ? persistedSyncResults : results;
}
@SuppressWarnings("unchecked")
@RequiredPermission(Permission.MANAGE_REPOSITORIES)
public PageList<RepoSyncResults> getRepoSyncResults(Subject subject, int repoId, PageControl pc) {
pc.initDefaultOrderingField("cssr.startTime", PageOrdering.DESC);
Query query = PersistenceUtility.createQueryWithOrderBy(entityManager,
RepoSyncResults.QUERY_GET_ALL_BY_REPO_ID, pc);
Query countQuery = PersistenceUtility.createCountQuery(entityManager, RepoSyncResults.QUERY_GET_ALL_BY_REPO_ID);
query.setParameter("repoId", repoId);
countQuery.setParameter("repoId", repoId);
List<RepoSyncResults> results = query.getResultList();
long count = (Long) countQuery.getSingleResult();
return new PageList<RepoSyncResults>(results, (int) count, pc);
}
// we want this in its own tx so other tx's can see it immediately, even if calling method is already in a tx
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public RepoSyncResults mergeRepoSyncResults(RepoSyncResults results) {
RepoSyncResults retval = entityManager.merge(results);
return retval;
}
public RepoSyncResults getRepoSyncResults(int resultsId) {
return entityManager.find(RepoSyncResults.class, resultsId);
}
}