/* * RHQ Management Platform * Copyright (C) 2005-2015 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ package org.rhq.enterprise.server.bundle; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; 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.EntityNotFoundException; import javax.persistence.PersistenceContext; import javax.persistence.Query; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.params.HttpClientParams; import org.apache.http.impl.auth.BasicScheme; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.params.BasicHttpParams; import org.apache.http.params.HttpParams; import org.apache.maven.artifact.versioning.ComparableVersion; import org.quartz.JobDetail; import org.quartz.Trigger; import org.rhq.core.clientapi.agent.bundle.BundleAgentService; import org.rhq.core.clientapi.agent.bundle.BundlePurgeRequest; import org.rhq.core.clientapi.agent.bundle.BundlePurgeResponse; import org.rhq.core.clientapi.agent.bundle.BundleScheduleRequest; import org.rhq.core.clientapi.agent.bundle.BundleScheduleResponse; import org.rhq.core.domain.auth.Subject; import org.rhq.core.domain.authz.Permission; import org.rhq.core.domain.authz.Role; import org.rhq.core.domain.bundle.Bundle; import org.rhq.core.domain.bundle.BundleDeployment; import org.rhq.core.domain.bundle.BundleDeploymentStatus; import org.rhq.core.domain.bundle.BundleDestination; import org.rhq.core.domain.bundle.BundleFile; import org.rhq.core.domain.bundle.BundleGroup; import org.rhq.core.domain.bundle.BundleNotFoundException; import org.rhq.core.domain.bundle.BundleResourceDeployment; import org.rhq.core.domain.bundle.BundleResourceDeploymentHistory; import org.rhq.core.domain.bundle.BundleType; import org.rhq.core.domain.bundle.BundleVersion; import org.rhq.core.domain.bundle.ResourceTypeBundleConfiguration; import org.rhq.core.domain.bundle.composite.BundleGroupAssignmentComposite; import org.rhq.core.domain.bundle.composite.BundleWithLatestVersionComposite; import org.rhq.core.domain.configuration.Configuration; import org.rhq.core.domain.configuration.ConfigurationUtility; import org.rhq.core.domain.configuration.PropertySimple; import org.rhq.core.domain.configuration.definition.ConfigurationDefinition; import org.rhq.core.domain.content.Architecture; import org.rhq.core.domain.content.Package; import org.rhq.core.domain.content.PackageCategory; import org.rhq.core.domain.content.PackageType; import org.rhq.core.domain.content.PackageVersion; import org.rhq.core.domain.content.Repo; import org.rhq.core.domain.criteria.BundleCriteria; import org.rhq.core.domain.criteria.BundleDeploymentCriteria; import org.rhq.core.domain.criteria.BundleDestinationCriteria; import org.rhq.core.domain.criteria.BundleFileCriteria; import org.rhq.core.domain.criteria.BundleGroupCriteria; import org.rhq.core.domain.criteria.BundleResourceDeploymentCriteria; import org.rhq.core.domain.criteria.BundleVersionCriteria; import org.rhq.core.domain.criteria.ResourceCriteria; import org.rhq.core.domain.criteria.ResourceGroupCriteria; import org.rhq.core.domain.criteria.ResourceTypeCriteria; import org.rhq.core.domain.criteria.RoleCriteria; import org.rhq.core.domain.resource.Resource; import org.rhq.core.domain.resource.ResourceType; import org.rhq.core.domain.resource.group.ResourceGroup; import org.rhq.core.domain.util.PageControl; import org.rhq.core.domain.util.PageList; import org.rhq.core.domain.util.StringUtils; import org.rhq.core.util.NumberUtil; import org.rhq.core.util.exception.ThrowableUtil; import org.rhq.core.util.stream.StreamUtil; import org.rhq.enterprise.server.RHQConstants; import org.rhq.enterprise.server.agentclient.AgentClient; 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.content.ContentManagerLocal; import org.rhq.enterprise.server.content.RepoManagerLocal; import org.rhq.enterprise.server.core.AgentManagerLocal; import org.rhq.enterprise.server.plugin.pc.bundle.BundleServerPluginManager; import org.rhq.enterprise.server.resource.ResourceManagerLocal; import org.rhq.enterprise.server.resource.ResourceTypeManagerLocal; import org.rhq.enterprise.server.resource.group.ResourceGroupManagerLocal; import org.rhq.enterprise.server.safeinvoker.HibernateDetachUtility; import org.rhq.enterprise.server.safeinvoker.HibernateDetachUtility.SerializationType; import org.rhq.enterprise.server.scheduler.SchedulerLocal; import org.rhq.enterprise.server.scheduler.jobs.BundleDeploymentStatusCheckJob; import org.rhq.enterprise.server.util.CriteriaQuery; import org.rhq.enterprise.server.util.CriteriaQueryExecutor; import org.rhq.enterprise.server.util.CriteriaQueryGenerator; import org.rhq.enterprise.server.util.CriteriaQueryRunner; import org.rhq.enterprise.server.util.LookupUtil; import org.rhq.enterprise.server.util.QuartzUtil; /** * Manages the creation and usage of bundles. * * @author John Mazzitelli * @author Ian Springer * @author Jay Shaughnessy */ @Stateless public class BundleManagerBean implements BundleManagerLocal, BundleManagerRemote { private static final Log LOG = LogFactory.getLog(BundleManagerBean.class); private static final String AUDIT_ACTION_DEPLOYMENT = "Deployment"; private static final String AUDIT_ACTION_DEPLOYMENT_REQUESTED = "Deployment Requested"; @PersistenceContext(unitName = RHQConstants.PERSISTENCE_UNIT_NAME) private EntityManager entityManager; @EJB private SubjectManagerLocal subjectManager; @EJB private AgentManagerLocal agentManager; @EJB private AuthorizationManagerLocal authorizationManager; @EJB private BundleManagerLocal bundleManager; @EJB private ContentManagerLocal contentManager; @EJB private RepoManagerLocal repoManager; @EJB private ResourceTypeManagerLocal resourceTypeManager; @EJB private ResourceGroupManagerLocal resourceGroupManager; @EJB private ResourceManagerLocal resourceManager; @EJB private SchedulerLocal quartzScheduler; @Override public ResourceTypeBundleConfiguration getResourceTypeBundleConfiguration(Subject subject, int compatGroupId) throws Exception { // Even though its harmless to return metadata (bundle config) about a resource type, we are getting that through // a relationship from a resource group. To prevent someone from probing the inventory to see which groups // are types that support bundles, we only allow someone to traverse the relationship from group to type // if that someone has access to the group. if (authorizationManager.canViewGroup(subject, compatGroupId)) { Query q = entityManager.createNamedQuery(ResourceType.QUERY_GET_BUNDLE_CONFIG_BY_GROUP_ID); q.setParameter("groupId", compatGroupId); ResourceTypeBundleConfiguration bundleConfig = null; try { Configuration config = (Configuration) q.getSingleResult(); if (config != null) { bundleConfig = new ResourceTypeBundleConfiguration(config); } } catch (EntityNotFoundException enfe) { // ignore this - this is just a group that isn't a compatible group // or it is, but its type cannot be a target for bundle deployments } return bundleConfig; } else { throw new Exception("[" + subject.getName() + "] is not authorized to access the group"); } } @Override @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public BundleResourceDeploymentHistory addBundleResourceDeploymentHistoryInNewTrans(Subject subject, int resourceDeploymentId, BundleResourceDeploymentHistory history) throws Exception { BundleResourceDeployment resourceDeployment = entityManager.find(BundleResourceDeployment.class, resourceDeploymentId); if (null == resourceDeployment) { throw new IllegalArgumentException("Invalid resourceDeploymentId: " + resourceDeploymentId); } resourceDeployment.addBundleResourceDeploymentHistory(history); this.entityManager.persist(resourceDeployment); return history; } @Override public List<BundleResourceDeploymentHistory> getBundleResourceDeploymentHistories(Subject subject, int resourceDeploymentId) { // First check if this user can actually see this resource deployment // by calling #findBundleResourceDeploymentsByCriteria) BundleResourceDeploymentCriteria criteria = new BundleResourceDeploymentCriteria(); criteria.addFilterId(resourceDeploymentId); criteria.fetchHistories(true); criteria.setPageControl(PageControl.getSingleRowInstance()); PageList<BundleResourceDeployment> bundleResourceDeploymentsByCriteria = findBundleResourceDeploymentsByCriteria( subject, criteria); if (bundleResourceDeploymentsByCriteria.isEmpty()) { return Collections.emptyList(); } BundleResourceDeployment resourceDeployment = bundleResourceDeploymentsByCriteria.iterator().next(); return new ArrayList<BundleResourceDeploymentHistory>(resourceDeployment.getBundleResourceDeploymentHistories()); } @Override public Bundle createBundle(Subject subject, String name, String description, int bundleTypeId, int[] bundleGroupIds) throws Exception { if (null == name || "".equals(name.trim())) { throw new IllegalArgumentException("Invalid bundleName: " + name); } BundleType bundleType = entityManager.find(BundleType.class, bundleTypeId); if (null == bundleType) { throw new IllegalArgumentException("Invalid bundleTypeId: " + bundleTypeId); } bundleGroupIds = (null != bundleGroupIds) ? bundleGroupIds : new int[0]; List<BundleGroup> bundleGroups = new ArrayList<BundleGroup>(bundleGroupIds.length); for (int bundleGroupId : bundleGroupIds) { BundleGroup bundleGroup = entityManager.find(BundleGroup.class, bundleGroupId); if (null == bundleGroup) { throw new IllegalArgumentException("Invalid bundleGroupId: " + bundleGroupId); } bundleGroups.add(bundleGroup); } checkCreateInitialBundleVersionAuthz(subject, bundleGroupIds); // create and add the required Repo. the Repo is a detached object which helps in its eventual removal. Repo repo = new Repo(name); repo.setCandidate(false); repo.setSyncSchedule(null); // create the repo as overlord, this allows users without MANAGE_INVENTORY permission to create bundles repo = repoManager.createRepo(subjectManager.getOverlord(), repo); // add the required PackageType. the PackageType is an attached object which helps in cascade removal // of packages in the bundle's repo. ResourceType resourceType = entityManager.find(ResourceType.class, bundleType.getResourceType().getId()); PackageType packageType = new PackageType(name, resourceType); packageType.setDescription("Package type for content of bundle " + name); packageType.setCategory(PackageCategory.BUNDLE); packageType.setSupportsArchitecture(false); packageType.setDisplayName(StringUtils.deCamelCase(name)); packageType.setDiscoveryInterval(-1L); packageType.setCreationData(false); packageType.setDeploymentConfigurationDefinition(null); Bundle bundle = new Bundle(name, bundleType, repo, packageType); bundle.setDescription(description); bundle.setPackageType(packageType); LOG.info("Creating bundle: " + bundle); entityManager.persist(bundle); for (BundleGroup bundleGroup : bundleGroups) { bundleGroup.addBundle(bundle); } return bundle; } @Override @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public BundleDeployment createBundleDeploymentInNewTrans(Subject subject, int bundleVersionId, int bundleDestinationId, String name, String description, Configuration configuration) throws Exception { BundleVersion bundleVersion = entityManager.find(BundleVersion.class, bundleVersionId); if (null == bundleVersion) { throw new IllegalArgumentException("Invalid bundleVersionId: " + bundleVersionId); } BundleDestination bundleDestination = entityManager.find(BundleDestination.class, bundleDestinationId); if (null == bundleDestination) { throw new IllegalArgumentException("Invalid bundleDestinationId: " + bundleDestinationId); } return createBundleDeploymentImpl(subject, bundleVersion, bundleDestination, name, description, configuration); } @Override public BundleDeployment createBundleDeployment(Subject subject, int bundleVersionId, int bundleDestinationId, String description, Configuration configuration) throws Exception { BundleVersion bundleVersion = entityManager.find(BundleVersion.class, bundleVersionId); if (null == bundleVersion) { throw new IllegalArgumentException("Invalid bundleVersionId: " + bundleVersionId); } BundleDestination bundleDestination = entityManager.find(BundleDestination.class, bundleDestinationId); if (null == bundleDestination) { throw new IllegalArgumentException("Invalid bundleDestinationId: " + bundleDestinationId); } checkDeployBundleAuthz(subject, bundleVersion.getBundle().getId(), bundleDestination.getGroup().getId()); String name = getBundleDeploymentNameImpl(subject, bundleDestination, bundleVersion, null); return this.createBundleDeploymentImpl(subject, bundleVersion, bundleDestination, name, description, configuration); } private BundleDeployment createBundleDeploymentImpl(Subject subject, BundleVersion bundleVersion, BundleDestination bundleDestination, String name, String description, Configuration configuration) throws Exception { ConfigurationDefinition configDef = bundleVersion.getConfigurationDefinition(); if (null != configDef) { if (null == configuration) { throw new IllegalArgumentException( "Missing Configuration. Configuration is required when the specified BundleVersion defines Configuration Properties."); } // passing in the default configuration will make sure that the readonly properties with the non-null // default values defined in the config def cannot be overridden by the caller. // Those properties are meant to be set by the bundle plugins, not by the user. Configuration defaultConfig = ConfigurationUtility.createDefaultConfiguration(configDef); List<String> errors = ConfigurationUtility.validateConfiguration(configuration, defaultConfig, configDef); if (!errors.isEmpty()) { throw new IllegalArgumentException("Invalid Configuration: " + errors.toString()); } } BundleDeployment deployment = new BundleDeployment(bundleVersion, bundleDestination, name); deployment.setDescription(description); deployment.setConfiguration(configuration); deployment.setSubjectName(subject.getName()); PropertySimple discoveryDelayProperty = configuration.getSimple("org.rhq.discoveryDelay"); if(discoveryDelayProperty != null) { deployment.setDiscoveryDelay(discoveryDelayProperty.getIntegerValue()); } entityManager.persist(deployment); return deployment; } @Override public BundleDestination createBundleDestination(Subject subject, int bundleId, String name, String description, String destinationSpecification, String deployDir, Integer groupId) throws Exception { // if there is a .. in the path that looks suspicious, reject it. (note the : is to reject things like C:..\..\dir on windows) // this won't allow everything (such as directories that start with ".." like "..abc" or "/abc/..def") but if you are naming // your directories in those strange ways, you deserve what you get if (deployDir != null && (deployDir.startsWith("..") || deployDir.matches(".*[/:\\\\]\\.\\..*"))) { throw new IllegalArgumentException( "Destination directories are not allowed to have '..' parent directory path elements"); } Bundle bundle = entityManager.find(Bundle.class, bundleId); if (null == bundle) { throw new IllegalArgumentException("Invalid bundleId [" + bundleId + "]"); } // validate that the group exists and is a compatible group that can support bundle deployments ResourceGroupCriteria c = new ResourceGroupCriteria(); c.addFilterId(groupId); c.addFilterBundleTargetableOnly(true); List<ResourceGroup> groups = resourceGroupManager.findResourceGroupsByCriteria(subject, c); if (null == groups || groups.isEmpty()) { throw new IllegalArgumentException("Invalid groupId [" + groupId + "]. It must be an existing compatible group whose members must be able to support bundle deployments"); } ResourceGroup group = entityManager.find(ResourceGroup.class, groups.get(0).getId()); checkDeployBundleAuthz(subject, bundle.getId(), groupId); // check that the resource group is compatible with the bundle type Set<ResourceType> targetedResourceTypes = bundle.getBundleType().getExplicitlyTargetedResourceTypes(); if (!targetedResourceTypes.isEmpty() && !targetedResourceTypes.contains(group.getResourceType())) { // the bundle type defines that it explicitly targets certain resource types but the current group // is not of that resource type. throw new IllegalArgumentException("Bundle of type [" + bundle.getBundleType().getName() + "] is incompatible with resource type " + group.getResourceType()); } // check that the destination specification is compatible with the bundle type String bundleType = bundle.getBundleType().getName(); ResourceType rt = group.getResourceType(); ResourceTypeBundleConfiguration bundleConfig = rt.getResourceTypeBundleConfiguration(); boolean found = false; for (ResourceTypeBundleConfiguration.BundleDestinationSpecification spec : bundleConfig .getAcceptableBundleDestinationSpecifications(bundleType)) { if (destinationSpecification.equals(spec.getName())) { found = true; break; } } if (!found) { throw new IllegalArgumentException("Destination specification '" + destinationSpecification + "' from resource type '" + rt.getName() + "' (plugin '" + rt.getPlugin() + "') is not compatible with bundle type '" + bundleType + "'."); } BundleDestination dest = new BundleDestination(bundle, name, group, destinationSpecification, deployDir); dest.setDescription(description); entityManager.persist(dest); return dest; } @Override public String getBundleDeploymentName(Subject subject, int bundleDestinationId, int bundleVersionId, int prevDeploymentId) { BundleDestination bundleDestination = entityManager.find(BundleDestination.class, bundleDestinationId); if (null == bundleDestination) { throw new IllegalArgumentException("Invalid bundleDestinationId: " + bundleDestinationId); } BundleVersion bundleVersion = null; BundleDeployment prevDeployment = null; if (bundleVersionId > 0) { bundleVersion = entityManager.find(BundleVersion.class, bundleVersionId); if (null == bundleVersion) { throw new IllegalArgumentException("Invalid bundleVersionId: " + bundleVersionId); } } else if (prevDeploymentId > 0) { prevDeployment = entityManager.find(BundleDeployment.class, prevDeploymentId); if (null == prevDeployment) { throw new IllegalArgumentException("Invalid prevDeploymentId: " + prevDeploymentId); } } else { throw new IllegalArgumentException("Must specify either a valid bundleVersionId [" + bundleVersionId + "] or prevDeploymentId [" + prevDeploymentId + "]"); } if (bundleVersion != null) { checkDeployBundleAuthz(subject, bundleVersion.getBundle().getId(), bundleDestination.getGroup().getId()); } return getBundleDeploymentNameImpl(subject, bundleDestination, bundleVersion, prevDeployment); } private String getBundleDeploymentNameImpl(Subject subject, BundleDestination bundleDestination, BundleVersion bundleVersion, BundleDeployment prevDeployment) { BundleDeploymentCriteria criteria = new BundleDeploymentCriteria(); criteria.addFilterDestinationId(bundleDestination.getId()); criteria.addFilterIsLive(true); criteria.fetchBundleVersion(true); List<BundleDeployment> liveDeployments = bundleManager.findBundleDeploymentsByCriteria(subject, criteria); BundleDeployment liveDeployment = (liveDeployments.isEmpty()) ? null : liveDeployments.get(0); String deploymentName; // always bump up the deploy number, whether its a revert or upgrade int deploy; boolean isInitialDeployment = (null == liveDeployment); if (isInitialDeployment) { deploy = 1; } else { try { String liveName = liveDeployment.getName(); int iStart = liveName.indexOf("[") + 1, iEnd = liveName.indexOf("]"); deploy = Integer.valueOf(liveName.substring(iStart, iEnd)) + 1; } catch (Exception e) { // if any odd error happens here, don't abort since this is only needed for the human readable name LOG.warn("Cannot determine next deployment number. Using -1. liveDeployment=" + liveDeployment); deploy = -1; } } if (null != bundleVersion) { String version = bundleVersion.getVersion(); String dest = bundleDestination.getName(); if (isInitialDeployment) { deploymentName = "Deployment [" + deploy + "] of Version [" + version + "] to [" + dest + "]"; } else { String liveVersion = liveDeployment.getBundleVersion().getVersion(); if (liveVersion.equals(version)) { // redeploy deploymentName = "Deployment [" + deploy + "] of Version [" + version + "] to [" + dest + "]"; } else { // upgrade deploymentName = "Deployment [" + deploy + "] of Version [" + version + "] to [" + dest + "]. Upgrade from Version [" + liveVersion + "]"; } } } else { // revert if (null == liveDeployment) { throw new IllegalArgumentException("Invalid Revert, no live deployment for destination" + bundleDestination); } deploymentName = "Deployment [" + deploy + "] Revert To: " + prevDeployment.getName(); } // BZ 790224 database column limit is 200 - we could be appending to old names, this string could get long if (deploymentName.length() > 200) { deploymentName = deploymentName.substring(0, 197) + "..."; } return deploymentName; } @Override @RequiredPermission(Permission.CREATE_BUNDLES) public BundleType createBundleType(Subject subject, String name, int resourceTypeId) throws Exception { if (null == name || "".equals(name.trim())) { throw new IllegalArgumentException("Invalid bundleTypeName: " + name); } ResourceType resourceType = entityManager.find(ResourceType.class, resourceTypeId); if (null == resourceType) { throw new IllegalArgumentException("Invalid resourceeTypeId: " + resourceTypeId); } BundleType bundleType = new BundleType(name, resourceType); entityManager.persist(bundleType); return bundleType; } /** * @deprecated since 4.13, see {@link org.rhq.enterprise.server.bundle.BundleManagerLocal#createBundleAndBundleVersion(org.rhq.core.domain.auth.Subject, * String, String, int, int[], String, String, String, String)} */ @Override @Deprecated public BundleVersion createBundleAndBundleVersion(Subject subject, String bundleName, String bundleDescription, int bundleTypeId, int[] bundleGroupIds, String bundleVersionName, String bundleVersionDescription, String version, String recipe) throws Exception { // first see if the bundle exists or not; if not, create one BundleCriteria criteria = new BundleCriteria(); criteria.setStrict(true); criteria.addFilterBundleTypeId(Integer.valueOf(bundleTypeId)); criteria.addFilterName(bundleName); criteria.clearPaging(); //disable paging as the code assumes all the results will be returned. PageList<Bundle> bundles = findBundlesByCriteria(subject, criteria); Bundle bundle; if (bundles.getTotalSize() == 0) { bundle = createBundle(subject, bundleName, bundleDescription, bundleTypeId, bundleGroupIds); } else { bundle = bundles.get(0); } //this portion is basically copied createBundleVersion, which is now deprecated and left to rot. if (null == bundleVersionName || "".equals(bundleVersionName.trim())) { throw new IllegalArgumentException("Invalid bundleVersionName: " + bundleVersionName); } checkCreateBundleVersionAuthz(subject, bundle.getId()); // parse the recipe (validation occurs here) and get the config def and list of files BundleType bundleType = bundle.getBundleType(); RecipeParseResults results; try { results = BundleManagerHelper.getPluginContainer().getBundleServerPluginManager() .parseRecipe(bundleType.getName(), recipe); } catch (Exception e) { // ensure that we throw a runtime exception to force a rollback throw new RuntimeException("Failed to parse recipe", e); } // now create the bundle version with the bundle we either found or created return createBundleVersionInternal(bundle, bundleVersionName, version, bundleVersionDescription, recipe, results.getConfigurationDefinition()); } /** * @deprecated since 4.13, see * {@link BundleManagerLocal#createBundleVersion(org.rhq.core.domain.auth.Subject, int, String, String, String, String)} */ @Override @SuppressWarnings("unchecked") @Deprecated public BundleVersion createBundleVersion(Subject subject, int bundleId, String name, String description, String version, String recipe) throws Exception { if (null == name || "".equals(name.trim())) { throw new IllegalArgumentException("Invalid bundleVersionName: " + name); } Bundle bundle = entityManager.find(Bundle.class, bundleId); if (null == bundle) { throw new IllegalArgumentException("Invalid bundleId: " + bundleId); } checkCreateBundleVersionAuthz(subject, bundleId); // parse the recipe (validation occurs here) and get the config def and list of files BundleType bundleType = bundle.getBundleType(); RecipeParseResults results; try { results = BundleManagerHelper.getPluginContainer().getBundleServerPluginManager() .parseRecipe(bundleType.getName(), recipe); } catch (Exception e) { // ensure that we throw a runtime exception to force a rollback throw new RuntimeException("Failed to parse recipe", e); } return createBundleVersionInternal(bundle, name, version, description, recipe, results.getConfigurationDefinition()); } @Override public BundleVersion createBundleVersionInternal(Bundle bundle, String name, String version, String description, String recipe, ConfigurationDefinition configurationDefinition) throws Exception { // ensure we have a version version = getVersion(version, bundle); ComparableVersion comparableVersion = new ComparableVersion(version); Query q = entityManager.createNamedQuery(BundleVersion.QUERY_FIND_VERSION_INFO_BY_BUNDLE_ID); q.setParameter("bundleId", bundle.getId()); @SuppressWarnings("unchecked") List<Object[]> list = (List<Object[]>) q.getResultList(); int versionOrder = list.size(); boolean needToUpdateOrder = false; // find out where in the order of versions this new version should be placed (e.g. 2.0 is after 1.0). // the query returns a list of arrays - first element in array is version; second is versionOrder // the query returns list in desc order - since the normal case is we are creating the latest, highest version, // starting at the current highest version is the most efficient (we'll break the for loop after 1 iteration). for (Object[] bv : list) { ComparableVersion bvv = new ComparableVersion(bv[0].toString()); int comparison = comparableVersion.compareTo(bvv); if (comparison == 0) { throw new RuntimeException("Cannot create bundle with version [" + version + "], it already exists"); } else if (comparison < 0) { versionOrder = ((Number) bv[1]).intValue(); needToUpdateOrder = true; } else { break; // Comparison > 0, means our new version is higher than what's in the DB, because we DESC ordered, we can stop } } if (needToUpdateOrder) { entityManager.flush(); q = entityManager.createNamedQuery(BundleVersion.UPDATE_VERSION_ORDER_BY_BUNDLE_ID); q.setParameter("bundleId", bundle.getId()); q.setParameter("versionOrder", versionOrder); q.executeUpdate(); entityManager.flush(); entityManager.clear(); } BundleVersion bundleVersion = new BundleVersion(name, version, bundle, recipe); bundleVersion.setVersionOrder(versionOrder); bundleVersion.setDescription(description); bundleVersion.setConfigurationDefinition(configurationDefinition); entityManager.persist(bundleVersion); return bundleVersion; } @Override @TransactionAttribute(TransactionAttributeType.NEVER) public BundleVersion createBundleVersionViaRecipe(Subject subject, String recipe) throws Exception { return createBundleVersionViaRecipeImpl(subject, recipe, false, null); } @Override @TransactionAttribute(TransactionAttributeType.NEVER) public BundleVersion createInitialBundleVersionViaRecipe(Subject subject, int[] bundleGroupIds, String recipe) throws Exception { return createBundleVersionViaRecipeImpl(subject, recipe, true, bundleGroupIds); } private BundleVersion createBundleVersionViaRecipeImpl(Subject subject, String recipe, boolean mustBeInitialVersion, int[] initialBundleGroupIds) throws Exception { BundleServerPluginManager manager = BundleManagerHelper.getPluginContainer().getBundleServerPluginManager(); BundleDistributionInfo info = manager.parseRecipe(recipe); BundleVersion bundleVersion = createBundleVersionViaDistributionInfo(subject, info, mustBeInitialVersion, initialBundleGroupIds); return bundleVersion; } @Override @TransactionAttribute(TransactionAttributeType.NEVER) public BundleVersion createBundleVersionViaFile(Subject subject, File distributionFile) throws Exception { return createBundleVersionViaFileImpl(subject, distributionFile, false, null); } @Override @TransactionAttribute(TransactionAttributeType.NEVER) public BundleVersion createBundleVersionViaContentHandle(Subject subject, String temporaryContentHandle) throws Exception { return createBundleVersionViaFileImpl(subject, contentManager.getTemporaryContentFile(temporaryContentHandle), false, null); } @Override @TransactionAttribute(TransactionAttributeType.NEVER) public BundleVersion createOrStoreBundleVersionViaFile(Subject subject, File distributionFile) throws Exception { try { return createBundleVersionViaFileImpl(subject, distributionFile, false, null); } catch (PermissionException e) { if (null != e.getCause() && e.getCause() instanceof BundleNotFoundException) { // This application exception indicates the special token handling workflow throw new BundleNotFoundException("[" + distributionFile.getName() + "]"); } else { throw e; } } } @Override @TransactionAttribute(TransactionAttributeType.NEVER) public BundleVersion createInitialBundleVersionViaFile(Subject subject, int[] bundleGroupIds, File distributionFile) throws Exception { return createBundleVersionViaFileImpl(subject, distributionFile, true, bundleGroupIds); } @Override @TransactionAttribute(TransactionAttributeType.NEVER) public BundleVersion createInitialBundleVersionViaContentHandle(Subject subject, int[] bundleGroupIds, String temporaryContentHandle) throws Exception { return createBundleVersionViaFileImpl(subject, contentManager.getTemporaryContentFile(temporaryContentHandle), true, bundleGroupIds); } private BundleVersion createBundleVersionViaFileImpl(Subject subject, File distributionFile, boolean mustBeInitialVersion, int[] initialBundleGroupIds) throws Exception { BundleServerPluginManager manager = BundleManagerHelper.getPluginContainer().getBundleServerPluginManager(); BundleDistributionInfo info = manager.processBundleDistributionFile(distributionFile); BundleVersion bundleVersion = createBundleVersionViaDistributionInfo(subject, info, mustBeInitialVersion, initialBundleGroupIds); return bundleVersion; } @Override @TransactionAttribute(TransactionAttributeType.NEVER) public BundleVersion createBundleVersionViaByteArray(Subject subject, byte[] fileBytes) throws Exception { return createBundleVersionViaByteArrayImpl(subject, fileBytes, false, null); } @Override @TransactionAttribute(TransactionAttributeType.NEVER) public BundleVersion createInitialBundleVersionViaByteArray(Subject subject, int[] bundleGroupIds, byte[] fileBytes) throws Exception { return createBundleVersionViaByteArrayImpl(subject, fileBytes, true, bundleGroupIds); } private BundleVersion createBundleVersionViaByteArrayImpl(Subject subject, byte[] fileBytes, boolean mustBeInitialVersion, int[] bundleGroupIds) throws Exception { File tmpFile = File.createTempFile("bundleDistroBits", ".zip"); try { StreamUtil.copy(new ByteArrayInputStream(fileBytes), new FileOutputStream(tmpFile)); BundleVersion bundleVersion = createBundleVersionViaFileImpl(subject, tmpFile, mustBeInitialVersion, bundleGroupIds); return bundleVersion; } finally { tmpFile.delete(); } } @Override @TransactionAttribute(TransactionAttributeType.NEVER) public BundleVersion createBundleVersionViaURL(Subject subject, String distributionFileUrl) throws Exception { return createBundleVersionViaURL(subject, distributionFileUrl, null, null); } @Override @TransactionAttribute(TransactionAttributeType.NEVER) public BundleVersion createBundleVersionViaURL(Subject subject, String distributionFileUrl, String username, String password) throws Exception { return createBundleVersionViaURLImpl(subject, distributionFileUrl, username, password, false, null); } @Override @TransactionAttribute(TransactionAttributeType.NEVER) public BundleVersion createInitialBundleVersionViaURL(Subject subject, int[] bundleGroupIds, String distributionFileUrl) throws Exception { return createInitialBundleVersionViaURL(subject, bundleGroupIds, distributionFileUrl, null, null); } @Override @TransactionAttribute(TransactionAttributeType.NEVER) public BundleVersion createInitialBundleVersionViaURL(Subject subject, int[] bundleGroupIds, String distributionFileUrl, String username, String password) throws Exception { return createBundleVersionViaURLImpl(subject, distributionFileUrl, username, password, true, bundleGroupIds); } public BundleVersion createBundleVersionViaURLImpl(Subject subject, String distributionFileUrl, String username, String password, boolean mustBeInitialVersion, int[] initialBundleGroupIds) throws Exception { File file = null; try { file = downloadFile(distributionFileUrl, username, password); if (LOG.isDebugEnabled()) { LOG.debug("Copied [" + file.length() + "] bytes from [" + distributionFileUrl + "] into [" + file.getPath() + "]"); } return createBundleVersionViaFileImpl(subject, file, mustBeInitialVersion, initialBundleGroupIds); } finally { if (file != null) { file.delete(); } } } @Override @TransactionAttribute(TransactionAttributeType.NEVER) public BundleVersion createOrStoreBundleVersionViaURL(Subject subject, String distributionFileUrl, String username, String password) throws Exception { File file = null; boolean deleteFile = true; try { file = downloadFile(distributionFileUrl, username, password); if (LOG.isDebugEnabled()) { LOG.debug("Copied [" + file.length() + "] bytes from [" + distributionFileUrl + "] into [" + file.getPath() + "]"); } return createBundleVersionViaFileImpl(subject, file, false, null); } catch (PermissionException e) { if (null != e.getCause() && e.getCause() instanceof BundleNotFoundException) { deleteFile = false; // This application exception indicates the special token handling workflow throw new BundleNotFoundException("[" + file.getName() + "]"); } else { throw e; } } finally { if (deleteFile && file != null) { file.delete(); } } } private File downloadFile(String fileUrl, String username, String password) throws IOException, URISyntaxException { URL url = new URL(fileUrl); if ("http".equalsIgnoreCase(url.getProtocol()) || "https".equalsIgnoreCase(url.getProtocol())) { return downloadFileFromHttp(url, username, password); } else { return slurp(url.openStream()); } } private File downloadFileFromHttp(URL url, String username, String password) throws URISyntaxException, IOException { HttpParams params = new BasicHttpParams(); HttpClientParams.setRedirecting(params, true); HttpClient httpClient = new DefaultHttpClient(params); HttpGet get = new HttpGet(url.toURI()); if (username != null) { get.addHeader(BasicScheme.authenticate(new UsernamePasswordCredentials(username, password), "UTF-8", false)); } HttpResponse response = httpClient.execute(get); if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { throw new IllegalArgumentException("Failed to download the file from the URL [" + url + "]. The server responded: " + response.getStatusLine().toString()); } InputStream contents = response.getEntity().getContent(); return slurp(contents); } private File slurp(InputStream is) throws IOException { File file = File.createTempFile("bundle-distribution", ".zip", new File(System.getProperty("jboss.server.temp.dir"))); StreamUtil.copy(is, new FileOutputStream(file)); return file; } private BundleVersion createBundleVersionViaDistributionInfo(Subject subject, BundleDistributionInfo info, boolean mustBeInitialVersion, int[] initialBundleGroupIds) throws Exception { // !!!! NEW BEHAVIOR SINCE 4.13 - we fail the bundle version creation when we cannot determine the set // of the files the bundle version should be comprised of. if (info.getBundleFiles() == null && info.getRecipeParseResults().getBundleFileNames() == null) { throw new IllegalArgumentException("Cannot create a bundle version without files determined by the recipe" + " or provided explicitly during bundle version creation."); } BundleType bundleType = bundleManager.getBundleType(subject, info.getBundleTypeName()); String bundleName = info.getRecipeParseResults().getBundleMetadata().getBundleName(); String bundleDescription = info.getRecipeParseResults().getBundleMetadata().getDescription(); String name = bundleName; String description = bundleDescription; String version = info.getRecipeParseResults().getBundleMetadata().getBundleVersion(); String recipe = info.getRecipe(); // first see if the bundle exists or not boolean createdBundle; BundleCriteria criteria = new BundleCriteria(); criteria.setStrict(true); criteria.addFilterBundleTypeId(bundleType.getId()); criteria.addFilterName(bundleName); PageList<Bundle> bundles = bundleManager.findBundlesByCriteria(subject, criteria); Bundle bundle; boolean isInitialVersion = (bundles.getTotalSize() == 0); if (!isInitialVersion && mustBeInitialVersion) { throw new PermissionException("This must be the initial version of a new Bundle."); } if (isInitialVersion) { bundle = bundleManager.createBundle(subject, bundleName, bundleDescription, bundleType.getId(), initialBundleGroupIds); createdBundle = true; } else { bundle = bundles.get(0); checkCreateBundleVersionAuthz(subject, bundle.getId()); createdBundle = false; } // now create the bundle version with the bundle we either found or created BundleVersion bundleVersion = bundleManager.createBundleVersionInternal(bundle, name, version, description, recipe, info.getRecipeParseResults().getConfigurationDefinition()); // now that we have the bundle version we can actually create the bundle files that were provided in // the bundle distribution try { Map<String, File> bundleFiles = info.getBundleFiles(); if (bundleFiles != null) { for (String fileName : bundleFiles.keySet()) { File file = bundleFiles.get(fileName); InputStream is = null; try { is = new FileInputStream(file); // peg the file version to the bundle version. In the future we may allow a distribution // to refer to existing versions of a file. BundleFile bundleFile = bundleManager.addBundleFile(subject, bundleVersion.getId(), fileName, bundleVersion.getVersion(), null, is); if (LOG.isDebugEnabled()) { LOG.debug("Added bundle file [" + bundleFile + "] to BundleVersion [" + bundleVersion + "]"); } } finally { safeClose(is); if (null != file) { file.delete(); } } } } } catch (Exception e) { // we failed to add one or more bundle files to the bundle version. Since this means the distribution file // did not fully get its bundle data persisted, we need to abort the entire effort. Let's delete // the bundle version including the bundle definition if we were the ones that initially created it // (thus this should completely wipe the database of any knowledge of what we just did previously) LOG.error("Failed to add bundle file to new bundle version [" + bundleVersion + "], will not create the new bundle", e); try { bundleManager.deleteBundleVersion(subjectManager.getOverlord(), bundleVersion.getId(), createdBundle); } catch (Exception e1) { LOG.error("Failed to delete the partially created bundle version: " + bundleVersion, e1); } throw e; } // because the distribution file can define things like bundle files and default tags, let's // ask for the full bundle version data so we can return that back to the caller; thus we let // the caller know exactly what the distribution file had inside of it and what we persisted to the DB BundleVersionCriteria bvCriteria = new BundleVersionCriteria(); bvCriteria.addFilterId(bundleVersion.getId()); bvCriteria.fetchBundle(true); bvCriteria.fetchBundleFiles(true); bvCriteria.fetchConfigurationDefinition(true); bvCriteria.fetchTags(true); PageList<BundleVersion> bundleVersions = bundleManager.findBundleVersionsByCriteria(subject, bvCriteria); if (bundleVersions != null && bundleVersions.size() == 1) { bundleVersion = bundleVersions.get(0); List<BundleFile> bundleFiles = bundleVersion.getBundleFiles(); if (bundleFiles != null && bundleFiles.size() > 0) { final BundleFileCriteria bfCriteria = new BundleFileCriteria(); bfCriteria.addFilterBundleVersionId(bundleVersion.getId()); bfCriteria.fetchPackageVersion(true); //Use CriteriaQuery to automatically chunk/page through criteria query results CriteriaQueryExecutor<BundleFile, BundleFileCriteria> queryExecutor = new CriteriaQueryExecutor<BundleFile, BundleFileCriteria>() { @Override public PageList<BundleFile> execute(BundleFileCriteria criteria) { return bundleManager.findBundleFilesByCriteria(subjectManager.getOverlord(), bfCriteria); } }; CriteriaQuery<BundleFile, BundleFileCriteria> bfs = new CriteriaQuery<BundleFile, BundleFileCriteria>( bfCriteria, queryExecutor); bundleFiles.clear(); for (BundleFile bf : bfs) { bundleFiles.add(bf); } } bundleVersion.setBundleDeployments(new ArrayList<BundleDeployment>()); } else { LOG.error("Failed to obtain the full bundle version, returning only what we currently know about it: " + bundleVersion); } return bundleVersion; } @SuppressWarnings("unchecked") private String getVersion(String version, Bundle bundle) { if (null != version && version.trim().length() > 0) { return version; } BundleVersion latestBundleVersion = null; Query q = entityManager.createNamedQuery(BundleVersion.QUERY_FIND_LATEST_BY_BUNDLE_ID); q.setParameter("bundleId", bundle.getId()); List<BundleVersion> list = q.getResultList(); if (list.size() > 0) { if (list.size() == 1) { latestBundleVersion = list.get(0); } else { throw new RuntimeException("Bundle [" + bundle.getName() + "] (id=" + bundle.getId() + ") has more than 1 'latest' version. This should not happen - aborting"); } } // note - this is the same algo used by ResourceClientProxy in updatebackingContent (for a resource) String latestVersion = latestBundleVersion != null ? latestBundleVersion.getVersion() : null; String newVersion = NumberUtil.autoIncrementVersion(latestVersion); return newVersion; } @Override public BundleFile addBundleFile(Subject subject, int bundleVersionId, String name, String version, Architecture architecture, InputStream fileStream) throws Exception { if (null == name || "".equals(name.trim())) { throw new IllegalArgumentException("Invalid bundleFileName: " + name); } if (null == version || "".equals(version.trim())) { throw new IllegalArgumentException("Invalid bundleFileVersion: " + version); } if (null == fileStream) { throw new IllegalArgumentException("Invalid fileStream: " + null); } BundleVersion bundleVersion = entityManager.find(BundleVersion.class, bundleVersionId); if (null == bundleVersion) { throw new IllegalArgumentException("Invalid bundleVersionId: " + bundleVersionId); } // Check authorization checkCreateBundleVersionAuthz(subject, bundleVersion.getBundle().getId()); // Create the PackageVersion the BundleFile is tied to. This implicitly creates the // Package for the PackageVersion. Bundle bundle = bundleVersion.getBundle(); PackageType packageType = bundle.getPackageType(); architecture = (null == architecture) ? contentManager.getNoArchitecture() : architecture; if (architecture.getId() == 0) { Query q = entityManager.createNamedQuery(Architecture.QUERY_FIND_BY_NAME); q.setParameter("name", architecture.getName()); architecture = (Architecture) q.getSingleResult(); } PackageVersion packageVersion = contentManager.createPackageVersionWithDisplayVersion(subject, name, packageType.getId(), version, null, architecture.getId(), fileStream); // set the PackageVersion's filename to the bundleFile name, it's left null by default packageVersion.setFileName(name); packageVersion = entityManager.merge(packageVersion); // Create the mapping between the Bundle's Repo and the BundleFile's PackageVersion Repo repo = bundle.getRepo(); // add the packageVersion as overlord, this allows users without MANAGE_INVENTORY permission to add bundle files repoManager.addPackageVersionsToRepo(subjectManager.getOverlord(), repo.getId(), new int[] { packageVersion.getId() }); // Classify the Package with the Bundle name in order to distinguish it from the same package name for // a different bundle. Package generalPackage = packageVersion.getGeneralPackage(); generalPackage.setClassification(bundle.getName()); // With all the plumbing in place, create and persist the BundleFile. Tie it to the Package if the caller // wants this BundleFile pinned to themost recent version. BundleFile bundleFile = new BundleFile(); bundleFile.setBundleVersion(bundleVersion); bundleFile.setPackageVersion(packageVersion); entityManager.persist(bundleFile); return bundleFile; } @Override public BundleFile addBundleFileViaByteArray(Subject subject, int bundleVersionId, String name, String version, Architecture architecture, byte[] fileBytes) throws Exception { return addBundleFile(subject, bundleVersionId, name, version, architecture, new ByteArrayInputStream(fileBytes)); } @Override public BundleFile addBundleFileViaURL(Subject subject, int bundleVersionId, String name, String version, Architecture architecture, String bundleFileUrl) throws Exception { // validate by immediately creating a URL URL url = new URL(bundleFileUrl); return addBundleFile(subject, bundleVersionId, name, version, architecture, url.openStream()); } @Override @TransactionAttribute(TransactionAttributeType.NEVER) public BundleFile addBundleFileViaURL(Subject subject, int bundleVersionId, String name, String version, Architecture architecture, String bundleFileUrl, String userName, String password) throws Exception { // Check authorization prior to performing any file download BundleVersion bundleVersion = entityManager.find(BundleVersion.class, bundleVersionId); if (null == bundleVersion) { throw new IllegalArgumentException("Invalid bundleVersionId: " + bundleVersionId); } checkCreateBundleVersionAuthz(subject, bundleVersion.getBundle().getId()); File file = null; FileInputStream fis = null; try { file = downloadFile(bundleFileUrl, userName, password); fis = new FileInputStream(file); return addBundleFile(subject, bundleVersionId, name, version, architecture, fis); } finally { if (fis != null) { safeClose(fis); } if (file != null) { file.delete(); } } } @Override public BundleFile addBundleFileViaPackageVersion(Subject subject, int bundleVersionId, String name, int packageVersionId) throws Exception { if (null == name || "".equals(name.trim())) { throw new IllegalArgumentException("Invalid bundleFileName: " + name); } BundleVersion bundleVersion = entityManager.find(BundleVersion.class, bundleVersionId); if (null == bundleVersion) { throw new IllegalArgumentException("Invalid bundleVersionId: " + bundleVersionId); } PackageVersion packageVersion = entityManager.find(PackageVersion.class, packageVersionId); if (null == packageVersion) { throw new IllegalArgumentException("Invalid packageVersionId: " + packageVersionId); } // Check authorization checkCreateBundleVersionAuthz(subject, bundleVersion.getBundle().getId()); // With all the plumbing in place, create and persist the BundleFile. Tie it to the Package if the caller // wants this BundleFile pinned to the most recent version. BundleFile bundleFile = new BundleFile(); bundleFile.setBundleVersion(bundleVersion); bundleFile.setPackageVersion(packageVersion); entityManager.persist(bundleFile); return bundleFile; } @Override @TransactionAttribute(TransactionAttributeType.NEVER) public void purgeBundleDestination(final Subject subject, int bundleDestinationId) throws Exception { // find the live bundle deployment for this destination, and get all the resource deployments for that live deployment BundleDeploymentCriteria bdc = new BundleDeploymentCriteria(); bdc.addFilterDestinationId(bundleDestinationId); bdc.addFilterIsLive(true); bdc.fetchBundleVersion(true); bdc.fetchResourceDeployments(true); bdc.fetchDestination(true); bdc.fetchConfiguration(true); List<BundleDeployment> liveDeployments = bundleManager.findBundleDeploymentsByCriteria(subject, bdc); if (1 != liveDeployments.size()) { throw new IllegalArgumentException("No live deployment to purge is found for destinationId [" + bundleDestinationId + "]"); } BundleDeployment liveDeployment = liveDeployments.get(0); List<BundleResourceDeployment> resourceDeploys = liveDeployment.getResourceDeployments(); if (resourceDeploys == null || resourceDeploys.isEmpty()) { return; // nothing to do } // Although they likely will, the actual resource deployments may no longer match the members of the destination's // resource group (if group membership has changed). We still use the group for authz because that is the more // intuitive, manageable way to do this. Otherwise the subject would need view perms on each of the previously // deployed-to resources, and that may be tricky to provide. checkDeployBundleAuthz(subject, liveDeployment.getBundleVersion().getBundle().getId(), liveDeployment .getDestination().getGroup().getId()); // we need to obtain the bundle type (the remote plugin container needs it). our first criteria can't fetch this deep, we have to do another query. BundleVersionCriteria bvc = new BundleVersionCriteria(); bvc.addFilterId(liveDeployment.getBundleVersion().getId()); bvc.fetchBundle(true); // will eagerly fetch the bundle type PageList<BundleVersion> bvs = bundleManager.findBundleVersionsByCriteria(subject, bvc); liveDeployment.setBundleVersion(bvs.get(0)); // wire up the full bundle version back into the live deployment // the bundle type doesn't eagerly load the resource type - the remote plugin container needs that too ResourceTypeCriteria rtc = new ResourceTypeCriteria(); rtc.addFilterIgnored(null); // we are purging, so we don't care if this returns ignored types or not, just purge anyway rtc.addFilterBundleTypeId(liveDeployment.getBundleVersion().getBundle().getBundleType().getId()); PageList<ResourceType> rts = resourceTypeManager.findResourceTypesByCriteria(subject, rtc); liveDeployment.getBundleVersion().getBundle().getBundleType().setResourceType(rts.get(0)); // we need to obtain the resources for all resource deployments - our first criteria can't fetch this deep, we have to do another query. List<Integer> resourceDeployIds = new ArrayList<Integer>(); for (BundleResourceDeployment resourceDeploy : resourceDeploys) { resourceDeployIds.add(resourceDeploy.getId()); } final BundleResourceDeploymentCriteria brdc = new BundleResourceDeploymentCriteria(); brdc.addFilterIds(resourceDeployIds.toArray(new Integer[resourceDeployIds.size()])); brdc.fetchResource(true); //Use CriteriaQuery to automatically chunk/page through criteria query results CriteriaQueryExecutor<BundleResourceDeployment, BundleResourceDeploymentCriteria> queryExecutor = new CriteriaQueryExecutor<BundleResourceDeployment, BundleResourceDeploymentCriteria>() { @Override public PageList<BundleResourceDeployment> execute(BundleResourceDeploymentCriteria criteria) { return bundleManager.findBundleResourceDeploymentsByCriteria(subject, brdc); } }; CriteriaQuery<BundleResourceDeployment, BundleResourceDeploymentCriteria> brdResults = new CriteriaQuery<BundleResourceDeployment, BundleResourceDeploymentCriteria>( brdc, queryExecutor); // PageList<BundleResourceDeployment> brdResults = bundleManager.findBundleResourceDeploymentsByCriteria(subject, // brdc); resourceDeploys.clear(); // resourceDeploys.addAll(brdResults); // need to wire the live bundle deployment back in - no need for another query or fetch it above because we have it already for (BundleResourceDeployment brd : brdResults) { resourceDeploys.add(brd); brd.setBundleDeployment(liveDeployment); } // loop through each deployment and purge it on agent Map<BundleResourceDeployment, String> failedToPurge = new HashMap<BundleResourceDeployment, String>(); for (BundleResourceDeployment resourceDeploy : resourceDeploys) { try { // first put the user name that requested the purge in the audit trail BundleResourceDeploymentHistory history = new BundleResourceDeploymentHistory(subject.getName(), "Purge Requested", "User [" + subject.getName() + "] requested to purge this deployment", null, BundleResourceDeploymentHistory.Status.SUCCESS, null, null); bundleManager.addBundleResourceDeploymentHistoryInNewTrans(subjectManager.getOverlord(), resourceDeploy.getId(), history); // get a connection to the agent and tell it to purge the bundle from the file system Subject overlord = subjectManager.getOverlord(); AgentClient agentClient = agentManager.getAgentClient(overlord, resourceDeploy.getResource().getId()); BundleAgentService bundleAgentService = agentClient.getBundleAgentService(); BundlePurgeRequest request = new BundlePurgeRequest(resourceDeploy); BundlePurgeResponse results = bundleAgentService.purge(request); if (!results.isSuccess()) { String errorMessage = results.getErrorMessage(); failedToPurge.put(resourceDeploy, errorMessage); } } catch (Exception e) { String errorMessage = ThrowableUtil.getStackAsString(e); failedToPurge.put(resourceDeploy, errorMessage); } } // marks the live deployment "no longer live" bundleManager._finalizePurge(subjectManager.getOverlord(), liveDeployment, failedToPurge); // throw an exception if we failed to purge one or more resource deployments. // since we are not in a tx context, we lose nothing. All DB updates have already been committed by now // which is what we want. All this does is inform the caller something went wrong. if (!failedToPurge.isEmpty()) { int totalDeployments = liveDeployment.getResourceDeployments().size(); int failedPurges = failedToPurge.size(); throw new Exception("Failed to purge [" + failedPurges + "] of [" + totalDeployments + "] remote resource deployments"); } } @Override public void _finalizePurge(Subject subject, BundleDeployment bundleDeployment, Map<BundleResourceDeployment, String> failedToPurge) throws Exception { bundleDeployment = entityManager.find(BundleDeployment.class, bundleDeployment.getId()); if (failedToPurge.isEmpty()) { bundleDeployment.setLive(false); // all deployments are purged, no where is this live anymore bundleDeployment.setErrorMessage(null); bundleDeployment.setStatus(BundleDeploymentStatus.SUCCESS); } else { bundleDeployment.setLive(true); // not all deployments are purged - error indicates it is still live somewhere StringBuilder errorStr = new StringBuilder(); int totalDeployments = bundleDeployment.getResourceDeployments().size(); int failedPurges = failedToPurge.size(); if (failedPurges < totalDeployments) { bundleDeployment.setStatus(BundleDeploymentStatus.MIXED); // some deployments were purged, so show MIXED status errorStr.append("Failed to purge [").append(failedPurges).append("] of [").append(totalDeployments) .append("] remote resource deployments"); } else { bundleDeployment.setStatus(BundleDeploymentStatus.FAILURE); // all deployments failed to be purged errorStr.append("Failed to purge all [").append(failedPurges).append("] remote resource deployments"); } // key is the resource deployment that failed to be purged; value is the error message for (Map.Entry<BundleResourceDeployment, String> entry : failedToPurge.entrySet()) { errorStr.append("\n\n"); errorStr.append(entry.getKey().getResource().getName()).append(": ").append(entry.getValue()); } bundleDeployment.setErrorMessage(errorStr.toString()); } } @Override public BundleDeployment scheduleBundleDeployment(Subject subject, int bundleDeploymentId, boolean isCleanDeployment) throws Exception { return scheduleBundleDeploymentImpl(subject, bundleDeploymentId, isCleanDeployment, false, null); } @Override public BundleDeployment scheduleRevertBundleDeployment(Subject subject, int bundleDestinationId, String deploymentDescription, boolean isCleanDeployment) throws Exception { BundleDeploymentCriteria c = new BundleDeploymentCriteria(); c.addFilterDestinationId(bundleDestinationId); c.addFilterIsLive(true); c.fetchDestination(true); List<BundleDeployment> liveDeployments = bundleManager.findBundleDeploymentsByCriteria(subject, c); if (1 != liveDeployments.size()) { throw new IllegalArgumentException("No live deployment found for destinationId [" + bundleDestinationId + "]"); } BundleDeployment liveDeployment = liveDeployments.get(0); Integer prevDeploymentId = liveDeployment.getReplacedBundleDeploymentId(); if (null == prevDeploymentId) { throw new IllegalArgumentException( "Live deployment [" + liveDeployment + "] can not be reverted. The Live deployment is either an initial deployment or a reverted deployment for destinationId [" + bundleDestinationId + "]"); } BundleDeployment prevDeployment = entityManager.find(BundleDeployment.class, prevDeploymentId); if (null == prevDeployment) { throw new IllegalArgumentException("Live deployment [" + liveDeployment + "] can not be reverted. There is no prior deployment for destinationId [" + bundleDestinationId + "]"); } checkDeployBundleAuthz(subject, liveDeployment.getBundleVersion().getBundle().getId(), liveDeployment .getDestination().getGroup().getId()); // A revert is done by deploying a new deployment that mirrors "prevDeployment". It uses the same // bundleVersion, destination and config as prevDeployment. It can have a new name and new desc, and // may opt to clean the deploy dir. It must be a new deployment so that all status/auditing/history starts // fresh and can be tracked. The key difference in the schedule request is that we set isRevert=true, // tell the bundle handler that we are in fact reverting from the current live deployment. The // deployment creation is done in a new transaction so it can then be scheduled. String name = getBundleDeploymentNameImpl(subject, liveDeployment.getDestination(), null, prevDeployment); String desc = (null != deploymentDescription) ? deploymentDescription : prevDeployment.getDescription(); Configuration config = (null == prevDeployment.getConfiguration()) ? null : prevDeployment.getConfiguration() .deepCopy(false); BundleDeployment revertDeployment = bundleManager.createBundleDeploymentInNewTrans(subject, prevDeployment .getBundleVersion().getId(), bundleDestinationId, name, desc, config); return scheduleBundleDeploymentImpl(subject, revertDeployment.getId(), isCleanDeployment, true, prevDeployment.getReplacedBundleDeploymentId()); } // revertedDeploymentReplacedDeployment is only meaningful if isRevert is true private BundleDeployment scheduleBundleDeploymentImpl(Subject subject, int bundleDeploymentId, boolean isCleanDeployment, boolean isRevert, Integer revertedDeploymentReplacedDeployment) throws Exception { // This work must be committed before we schedule the status check job, so do it in a new trans BundleDeployment newDeployment = bundleManager.scheduleBundleDeploymentInNewTransaction(subject, bundleDeploymentId, isCleanDeployment, isRevert, revertedDeploymentReplacedDeployment); // schedule the bundle deployment completion check. Due to timing issues, we cannot determine // the overall completion status of the bundle deployment while receiving the individual resource // deployment statuses. This needs to be done out of band by a quartz job. // See https://bugzilla.redhat.com/show_bug.cgi?id=1003679 for details. try { // Just set to trigger the first time. The job will set another trigger if necessary. We // saw in some cases the future triggers stacking up. Whether it was a real problem or not // I'm not sure, but this avoids the potential issue. JobDetail jobDetail = BundleDeploymentStatusCheckJob.getJobDetail(bundleDeploymentId); Trigger trigger = QuartzUtil.getFireOnceImmediateTrigger(jobDetail); quartzScheduler.scheduleJob(jobDetail, trigger); } catch (Exception e) { LOG.error("Failed to schedule bundle deployment status check job for deployment:" + newDeployment, e); } return newDeployment; } @Override @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public BundleDeployment scheduleBundleDeploymentInNewTransaction(Subject subject, int bundleDeploymentId, boolean isCleanDeployment, boolean isRevert, Integer revertedDeploymentReplacedDeployment) throws Exception { BundleDeployment newDeployment = entityManager.find(BundleDeployment.class, bundleDeploymentId); if (null == newDeployment) { throw new IllegalArgumentException("Invalid bundleDeploymentId: " + bundleDeploymentId); } BundleDestination destination = newDeployment.getDestination(); ResourceGroup group = destination.getGroup(); // Create and persist updates for each of the group members. Set<Resource> groupMembers = group.getExplicitResources(); if (groupMembers.isEmpty()) { throw new IllegalArgumentException("Destination [" + destination + "] group has no members. Invalid deployment destination"); } checkDeployBundleAuthz(subject, newDeployment.getBundleVersion().getBundle().getId(), group.getId()); for (Resource groupMember : groupMembers) { try { scheduleBundleResourceDeployment(subject, newDeployment, groupMember, isCleanDeployment, isRevert); } catch (Throwable t) { LOG.error("Failed to complete scheduling of bundle deployment to [" + groupMember + "]. Other bundle deployments to other resources may have been scheduled. ", t); } } // make sure the new deployment is set as the live deployment and properly replaces the // previously live deployment. destination = entityManager.find(BundleDestination.class, destination.getId()); List<BundleDeployment> currentDeployments = destination.getDeployments(); if (null != currentDeployments) { for (BundleDeployment d : currentDeployments) { if (d.isLive()) { d.setLive(false); if (!isRevert) { newDeployment.setReplacedBundleDeploymentId(d.getId()); } else { // we are doing a revert; so our "replacedDeployment" should be what the deployment we // are reverting to replaced. For example, assume I deployed three bundles: // Deployment #1 - replaced nothing (hence replacedBundleDeploymentId == null) // Deployment #2 - replaced #1 // Deployment #3 - replaced #2 // Now do a revert. Reverting the live deployment #3 means we really want to re-deploy #2. // This new deployment gets a new ID of #4, but it is actually a deployment equivalent to #2. // If our deploy #4 is actually a redeploy of #2, we need to prepare for the user wanting // to revert #4 by setting the replacedBundleDeploymentId to that which #2 had - this being #1. // Deployment #4 - replaced #1 // Now if we ask to revert #4, we will actually be re-deploying #1, which is what we want. // This allows us to revert back multiple steps. newDeployment.setReplacedBundleDeploymentId(revertedDeploymentReplacedDeployment); } break; } } } newDeployment.setLive(true); return newDeployment; } private BundleResourceDeployment scheduleBundleResourceDeployment(Subject subject, BundleDeployment deployment, Resource bundleTarget, boolean isCleanDeployment, boolean isRevert) throws Exception { int bundleTargetResourceId = bundleTarget.getId(); AgentClient agentClient = agentManager.getAgentClient(subjectManager.getOverlord(), bundleTargetResourceId); BundleAgentService bundleAgentService = agentClient.getBundleAgentService(); // The BundleResourceDeployment record must exist in the db before the agent request because the agent may try // to add History to it during immediate deployments. So, create and persist it (requires a new trans). BundleResourceDeployment resourceDeployment = bundleManager.createBundleResourceDeploymentInNewTrans( subjectManager.getOverlord(), deployment.getId(), bundleTargetResourceId); if (null != bundleTarget.getResourceType().getResourceTypeBundleConfiguration()) { // Ask the agent to schedule the request. The agent should add history as needed. try { BundleScheduleRequest request = bundleManager.getScheduleRequest(subject, resourceDeployment.getId(), isCleanDeployment, isRevert); // add the deployment request history (in a new trans) BundleResourceDeploymentHistory history = new BundleResourceDeploymentHistory(subject.getName(), AUDIT_ACTION_DEPLOYMENT_REQUESTED, deployment.getName(), null, BundleResourceDeploymentHistory.Status.SUCCESS, "Requested deployment time: " + request.getRequestedDeployTimeAsString(), null); bundleManager.addBundleResourceDeploymentHistoryInNewTrans(subjectManager.getOverlord(), resourceDeployment.getId(), history); BundleScheduleResponse response = bundleAgentService.schedule(request); if (!response.isSuccess()) { // Handle Schedule Failures. This may include deployment failures for immediate deployment request bundleManager.setBundleResourceDeploymentStatusInNewTransaction(subject, resourceDeployment.getId(), BundleDeploymentStatus.FAILURE); history = new BundleResourceDeploymentHistory(subject.getName(), AUDIT_ACTION_DEPLOYMENT, deployment.getName(), null, BundleResourceDeploymentHistory.Status.FAILURE, response.getErrorMessage(), null); bundleManager.addBundleResourceDeploymentHistoryInNewTrans(subject, resourceDeployment.getId(), history); } } catch (Throwable t) { // fail the unlaunched resource deployment BundleResourceDeploymentHistory failureHistory = new BundleResourceDeploymentHistory(subject.getName(), AUDIT_ACTION_DEPLOYMENT, deployment.getName(), null, BundleResourceDeploymentHistory.Status.FAILURE, "Failed to schedule, agent on [" + bundleTarget + "] may be down: " + t, null); bundleManager.addBundleResourceDeploymentHistoryInNewTrans(subject, resourceDeployment.getId(), failureHistory); bundleManager.setBundleResourceDeploymentStatusInNewTransaction(subject, resourceDeployment.getId(), BundleDeploymentStatus.FAILURE); } } else { bundleManager.setBundleResourceDeploymentStatusInNewTransaction(subject, resourceDeployment.getId(), BundleDeploymentStatus.FAILURE); BundleResourceDeploymentHistory history = new BundleResourceDeploymentHistory(subject.getName(), AUDIT_ACTION_DEPLOYMENT, deployment.getName(), null, BundleResourceDeploymentHistory.Status.FAILURE, "Target resource is not of a type that can have bundles deployed to it [resource=" + bundleTarget.getName() + "; id=" + bundleTarget.getId() + "]. Fix target group for destination [" + deployment.getDestination().getName() + "]", null); bundleManager.addBundleResourceDeploymentHistoryInNewTrans(subject, resourceDeployment.getId(), history); } return resourceDeployment; } @Override @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED) public BundleScheduleRequest getScheduleRequest(Subject subject, int resourceDeploymentId, boolean isCleanDeployment, boolean isRevert) throws Exception { // make sure the deployment contains the info required by the schedule service BundleResourceDeploymentCriteria brdc = new BundleResourceDeploymentCriteria(); brdc.addFilterId(resourceDeploymentId); brdc.fetchResource(true); brdc.fetchBundleDeployment(true); List<BundleResourceDeployment> resourceDeployments = bundleManager.findBundleResourceDeploymentsByCriteria( subject, brdc); if (null == resourceDeployments || resourceDeployments.isEmpty()) { throw new IllegalArgumentException("Can not deploy using invalid resourceDeploymentId [" + resourceDeploymentId + "]."); } BundleResourceDeployment resourceDeployment = resourceDeployments.get(0); ResourceCriteria rc = new ResourceCriteria(); rc.addFilterId(resourceDeployment.getResource().getId()); rc.fetchTags(true); Resource resource = resourceManager.findResourcesByCriteria(subject, rc).get(0); resourceDeployment.setResource(resource); // make sure the deployment contains the info required by the schedule service BundleDeploymentCriteria bdc = new BundleDeploymentCriteria(); bdc.addFilterId(resourceDeployment.getBundleDeployment().getId()); bdc.fetchBundleVersion(true); bdc.fetchConfiguration(true); bdc.fetchDestination(true); BundleDeployment deployment = bundleManager.findBundleDeploymentsByCriteria(subject, bdc).get(0); BundleCriteria bc = new BundleCriteria(); bc.addFilterDestinationId(deployment.getDestination().getId()); Bundle bundle = bundleManager.findBundlesByCriteria(subject, bc).get(0); ResourceTypeCriteria rtc = new ResourceTypeCriteria(); rtc.addFilterIgnored(false); // we only care about those that are not ignored rtc.addFilterBundleTypeId(bundle.getBundleType().getId()); ResourceType resourceType = resourceTypeManager.findResourceTypesByCriteria(subject, rtc).get(0); bundle.getBundleType().setResourceType(resourceType); deployment.getBundleVersion().setBundle(bundle); deployment.getDestination().setBundle(bundle); resourceDeployment.setBundleDeployment(deployment); // now scrub the hibernate entity to make it a pojo suitable for sending to the client HibernateDetachUtility.nullOutUninitializedFields(resourceDeployment, SerializationType.SERIALIZATION); BundleScheduleRequest request = new BundleScheduleRequest(resourceDeployment); request.setCleanDeployment(isCleanDeployment); request.setRevert(isRevert); entityManager.clear(); return request; } @Override @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public BundleResourceDeployment createBundleResourceDeploymentInNewTrans(Subject subject, int bundleDeploymentId, int resourceId) throws Exception { BundleDeployment deployment = entityManager.find(BundleDeployment.class, bundleDeploymentId); if (null == deployment) { throw new IllegalArgumentException("Invalid bundleDeploymentId: " + bundleDeploymentId); } Resource resource = entityManager.find(Resource.class, resourceId); if (null == resource) { throw new IllegalArgumentException("Invalid resourceId (Resource does not exist): " + resourceId); } BundleResourceDeployment resourceDeployment = new BundleResourceDeployment(deployment, resource); entityManager.persist(resourceDeployment); return resourceDeployment; } @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) @Override public BundleResourceDeployment setBundleResourceDeploymentStatusInNewTransaction(Subject subject, int resourceDeploymentId, BundleDeploymentStatus status) throws Exception { // set the status of the individual resource deployment BundleResourceDeployment resourceDeployment = entityManager.find(BundleResourceDeployment.class, resourceDeploymentId); if (null == resourceDeployment) { throw new IllegalArgumentException("Invalid bundleDeploymentId: " + resourceDeploymentId); } // update the status resourceDeployment.setStatus(status); return resourceDeployment; } @Override public BundleDeploymentStatus determineBundleDeploymentStatus(int bundleDeploymentId) { BundleDeployment deployment = entityManager.find(BundleDeployment.class, bundleDeploymentId); if (deployment.getStatus().isTerminal()) { return deployment.getStatus(); } List<BundleResourceDeployment> deployments = deployment.getResourceDeployments(); boolean someInProgress = false; boolean someSuccess = false; boolean someFailure = false; for (BundleResourceDeployment rd : deployments) { switch (rd.getStatus()) { case SUCCESS: someSuccess = true; break; case FAILURE: someFailure = true; break; case IN_PROGRESS: someInProgress = true; break; } } if (someInProgress) { deployment.setStatus(BundleDeploymentStatus.IN_PROGRESS); } else if (someSuccess) { deployment.setStatus(someFailure ? BundleDeploymentStatus.MIXED : BundleDeploymentStatus.SUCCESS); } else { deployment.setStatus(BundleDeploymentStatus.FAILURE); } return deployment.getStatus(); } @Override public Set<String> getBundleVersionFilenames(Subject subject, int bundleVersionId, boolean withoutBundleFileOnly) throws Exception { BundleVersion bundleVersion = entityManager.find(BundleVersion.class, bundleVersionId); if (null == bundleVersion) { throw new IllegalArgumentException("Invalid bundleVersionId: " + bundleVersionId); } checkCreateBundleVersionAuthz(subject, bundleVersion.getBundle().getId()); Set<String> result = null; //new in 4.13 - we no longer throw an exception on failure to parse try { // parse the recipe (validation occurs here) and get the config def and list of files BundleType bundleType = bundleVersion.getBundle().getBundleType(); RecipeParseResults parseResults = BundleManagerHelper.getPluginContainer().getBundleServerPluginManager() .parseRecipe(bundleType.getName(), bundleVersion.getRecipe()); result = parseResults.getBundleFileNames(); } catch (Exception e) { if (LOG.isDebugEnabled()) { LOG.debug("Failed to parse the recipe of bundle version " + bundleVersionId + " while trying to get the list of bundle file names: " + e.getMessage()); } } if (result == null) { return Collections.emptySet(); } if (withoutBundleFileOnly) { List<BundleFile> bundleFiles = bundleVersion.getBundleFiles(); Set<String> allFilenames = result; result = new HashSet<String>(allFilenames.size() - bundleFiles.size()); for (String filename : allFilenames) { boolean found = false; for (BundleFile bundleFile : bundleFiles) { String name = bundleFile.getPackageVersion().getGeneralPackage().getName(); if (name.equals(filename)) { found = true; break; } } if (!found) { result.add(filename); } } } return result; } @Override public HashMap<String, Boolean> getAllBundleVersionFilenames(Subject subject, int bundleVersionId) throws Exception { BundleVersion bundleVersion = entityManager.find(BundleVersion.class, bundleVersionId); if (null == bundleVersion) { throw new IllegalArgumentException("Invalid bundleVersionId: " + bundleVersionId); } checkCreateBundleVersionAuthz(subject, bundleVersion.getBundle().getId()); // parse the recipe (validation occurs here) and get the config def and list of files try { BundleType bundleType = bundleVersion.getBundle().getBundleType(); RecipeParseResults parseResults = BundleManagerHelper.getPluginContainer().getBundleServerPluginManager() .parseRecipe(bundleType.getName(), bundleVersion.getRecipe()); Set<String> filenames = parseResults.getBundleFileNames(); HashMap<String, Boolean> result = new HashMap<String, Boolean>(filenames == null ? 0 : filenames.size()); if (filenames != null && !filenames.isEmpty()) { List<BundleFile> bundleFiles = bundleVersion.getBundleFiles(); for (String filename : filenames) { boolean found = false; for (BundleFile bundleFile : bundleFiles) { String name = bundleFile.getPackageVersion().getGeneralPackage().getName(); if (name.equals(filename)) { found = true; break; } } result.put(filename, found); } } return result; } catch (Exception e) { if (LOG.isDebugEnabled()) { LOG.debug("Failed to obtain the bundle files of a bundle version: " + bundleVersionId); } return new HashMap<String, Boolean>(0); } } @Override @SuppressWarnings("unchecked") public List<BundleType> getAllBundleTypes(Subject subject) { // the list of types will be small, no need to support paging Query q = entityManager.createNamedQuery(BundleType.QUERY_FIND_ALL); List<BundleType> types = q.getResultList(); return types; } @Override public BundleType getBundleType(Subject subject, String bundleTypeName) { // the list of types will be small, no need to support paging Query q = entityManager.createNamedQuery(BundleType.QUERY_FIND_BY_NAME); q.setParameter("name", bundleTypeName); BundleType type = (BundleType) q.getSingleResult(); return type; } @Override public PageList<BundleDeployment> findBundleDeploymentsByCriteria(Subject subject, BundleDeploymentCriteria criteria) { CriteriaQueryGenerator generator = new CriteriaQueryGenerator(subject, criteria); // filter by bundles that are viewable if (!authorizationManager.hasGlobalPermission(subject, Permission.VIEW_BUNDLES)) { generator.setAuthorizationBundleFragment(CriteriaQueryGenerator.AuthorizationTokenType.BUNDLE, subject.getId(), "bundleVersion.bundle"); } CriteriaQueryRunner<BundleDeployment> queryRunner = new CriteriaQueryRunner<BundleDeployment>(criteria, generator, entityManager); PageList<BundleDeployment> result = queryRunner.execute(); // The result currently holds bundle deployments for bundles viewable by the caller, but we must remove // those bundle deployments for destinations not viewable by the user. In essence we wanted two authz tokens, // one for bundle and one for resource group, but we can only supply one. if (!(result.isEmpty() || authorizationManager.isInventoryManager(subject))) { for (Iterator<BundleDeployment> i = result.iterator(); i.hasNext();) { BundleDeployment bd = i.next(); int groupId = bd.getDestination().getGroup().getId(); if (!authorizationManager.canViewGroup(subject, groupId)) { i.remove(); } } } return result; } @Override public PageList<BundleDestination> findBundleDestinationsByCriteria(Subject subject, BundleDestinationCriteria criteria) { CriteriaQueryGenerator generator = new CriteriaQueryGenerator(subject, criteria); // Filter by destinations (resource groups) that are viewable if (!authorizationManager.isInventoryManager(subject)) { generator.setAuthorizationResourceFragment(CriteriaQueryGenerator.AuthorizationTokenType.GROUP, subject.getId()); } CriteriaQueryRunner<BundleDestination> queryRunner = new CriteriaQueryRunner<BundleDestination>(criteria, generator, entityManager); return queryRunner.execute(); } @Override public PageList<BundleResourceDeployment> findBundleResourceDeploymentsByCriteria(Subject subject, BundleResourceDeploymentCriteria criteria) { CriteriaQueryGenerator generator = new CriteriaQueryGenerator(subject, criteria); // filter by bundles that are viewable if (!authorizationManager.hasGlobalPermission(subject, Permission.VIEW_BUNDLES)) { generator.setAuthorizationBundleFragment(CriteriaQueryGenerator.AuthorizationTokenType.BUNDLE, subject.getId(), "bundleDeployment.bundleVersion.bundle"); } CriteriaQueryRunner<BundleResourceDeployment> queryRunner = new CriteriaQueryRunner<BundleResourceDeployment>( criteria, generator, entityManager); PageList<BundleResourceDeployment> result = queryRunner.execute(); // if necessary, filter results to resources that are viewable if (!result.isEmpty() && criteria.isInventoryManagerRequired() && !authorizationManager.isInventoryManager(subject)) { // try to authorize in one call, if that fails then go (slow) fine-grained List<Integer> resourceIds = new ArrayList<Integer>(result.size()); for (BundleResourceDeployment brd : result) { int resourceId = brd.getResource().getId(); resourceIds.add(resourceId); } if (!authorizationManager.canViewResources(subject, resourceIds)) { for (Integer resourceId : resourceIds) { if (!authorizationManager.canViewResource(subject, resourceId)) { for (Iterator<BundleResourceDeployment> i = result.iterator(); i.hasNext();) { BundleResourceDeployment brd = i.next(); if (brd.getResource().getId() == resourceId.intValue()) { i.remove(); } } } } } } return result; } @Override public PageList<BundleVersion> findBundleVersionsByCriteria(Subject subject, BundleVersionCriteria criteria) { CriteriaQueryGenerator generator = new CriteriaQueryGenerator(subject, criteria); if (!authorizationManager.hasGlobalPermission(subject, Permission.VIEW_BUNDLES)) { generator.setAuthorizationBundleFragment(CriteriaQueryGenerator.AuthorizationTokenType.BUNDLE, subject.getId()); } CriteriaQueryRunner<BundleVersion> queryRunner = new CriteriaQueryRunner<BundleVersion>(criteria, generator, entityManager); PageList<BundleVersion> result = queryRunner.execute(); // If asking for optional data that the subject may not be able to see then ensure that the optional // data is filtered appropriately. In this case only deployments to destinations viewable by the subject // can be returned. The result currently holds bundle versions viewable by the caller, but the bundle version // may have been deployed to destinations for which the user does not have access to the destination's // resource group. (BZ 694741) if (!result.isEmpty() && criteria.isInventoryManagerRequired() && !authorizationManager.isInventoryManager(subject)) { // this works because findBundleDestinationsByCriteria() authorizes against resource group associations for (BundleVersion bundleVersion : result) { int numDeployments = bundleVersion.getBundleDeployments().size(); if (0 == numDeployments) { continue; } Bundle bundle = bundleVersion.getBundle(); BundleDestinationCriteria destinationCriteria = new BundleDestinationCriteria(); destinationCriteria.clearPaging(); //disable paging as the code assumes all the results will be returned. destinationCriteria.addFilterBundleId(bundle.getId()); // get the viewable destinations and use to filter the deployments List<BundleDestination> destinations = findBundleDestinationsByCriteria(subject, destinationCriteria); List<BundleDeployment> filteredDeployments = new ArrayList<BundleDeployment>(numDeployments); entityManager.detach(bundleVersion); // make sure we don't persist the filtered data for (BundleDeployment deployment : bundleVersion.getBundleDeployments()) { if (containsDestination(destinations, deployment.getDestination())) filteredDeployments.add(deployment); } bundleVersion.setBundleDeployments(filteredDeployments); } } return result; } private boolean containsDestination(List<BundleDestination> list, BundleDestination dest) { int id = dest.getId(); for (BundleDestination destination : list) { if (destination.getId() == id) return true; } return false; } @Override public PageList<BundleFile> findBundleFilesByCriteria(Subject subject, BundleFileCriteria criteria) { CriteriaQueryGenerator generator = new CriteriaQueryGenerator(subject, criteria); // filter by bundles that are viewable if (!authorizationManager.hasGlobalPermission(subject, Permission.VIEW_BUNDLES)) { generator.setAuthorizationBundleFragment(CriteriaQueryGenerator.AuthorizationTokenType.BUNDLE, subject.getId()); } CriteriaQueryRunner<BundleFile> queryRunner = new CriteriaQueryRunner<BundleFile>(criteria, generator, entityManager); return queryRunner.execute(); } @Override public PageList<Bundle> findBundlesByCriteria(Subject subject, BundleCriteria criteria) { CriteriaQueryGenerator generator = new CriteriaQueryGenerator(subject, criteria); if (!authorizationManager.hasGlobalPermission(subject, Permission.VIEW_BUNDLES)) { generator.setAuthorizationBundleFragment(CriteriaQueryGenerator.AuthorizationTokenType.BUNDLE, subject.getId(), null); } CriteriaQueryRunner<Bundle> queryRunner = new CriteriaQueryRunner<Bundle>(criteria, generator, entityManager); PageList<Bundle> result = queryRunner.execute(); // If asking for optional data that the subject may not be able to see then ensure that the optional // data is filtered appropriately. In this case only destinations viewable by the subject can be returned. // The result currently holds bundles viewable by the caller, but the bundle may have been deployed to // destinations for which the user does not have access to the destination's resource group. (BZ 694741) if (!result.isEmpty() && criteria.isInventoryManagerRequired() && !authorizationManager.isInventoryManager(subject)) { // this works because findBundleDestinationsByCriteria() authorizes against resource group associations for (Bundle bundle : result) { if (bundle.getDestinations().isEmpty()) { continue; } BundleDestinationCriteria destinationCriteria = new BundleDestinationCriteria(); destinationCriteria.clearPaging(); //disable paging as the code assumes all the results will be returned. destinationCriteria.addFilterBundleId(bundle.getId()); List<BundleDestination> destinations = findBundleDestinationsByCriteria(subject, destinationCriteria); entityManager.detach(bundle); // make sure the narrowed set of destinations does not get persisted bundle.setDestinations(destinations); } } return result; } @Override public PageList<BundleWithLatestVersionComposite> findBundlesWithLatestVersionCompositesByCriteria(Subject subject, BundleCriteria criteria) { CriteriaQueryGenerator generator = new CriteriaQueryGenerator(subject, criteria); String replacementSelectList = "" + " new org.rhq.core.domain.bundle.composite.BundleWithLatestVersionComposite( " + " bundle.id," + " bundle.name," + " bundle.description," + " ( SELECT bv1.version FROM bundle.bundleVersions bv1 WHERE bv1.versionOrder = (SELECT MAX(bv2.versionOrder) FROM BundleVersion bv2 WHERE bv2.bundle.id = bundle.id) ) AS latestVersion," + " ( SELECT COUNT(bv3) FROM bundle.bundleVersions bv3 WHERE bv3.bundle.id = bundle.id) AS deploymentCount ) "; generator.alterProjection(replacementSelectList); if (!authorizationManager.hasGlobalPermission(subject, Permission.VIEW_BUNDLES)) { generator.setAuthorizationBundleFragment(CriteriaQueryGenerator.AuthorizationTokenType.BUNDLE, subject.getId(), null); } CriteriaQueryRunner<BundleWithLatestVersionComposite> queryRunner = new CriteriaQueryRunner<BundleWithLatestVersionComposite>( criteria, generator, entityManager); PageList<BundleWithLatestVersionComposite> results = queryRunner.execute(); return results; } // to avoid deadlocks, you cannot delete multiple bundles concurrently (see BZ 606530) // instead, this simple method just loops over the given array and deletes them serially // note they all get deleted in their own transaction; this method is never in a tx itself @Override @TransactionAttribute(TransactionAttributeType.NEVER) public void deleteBundles(Subject subject, int[] bundleIds) throws Exception { if (bundleIds != null) { for (int bundleId : bundleIds) { bundleManager.deleteBundle(subject, bundleId); } } } @Override @SuppressWarnings("unchecked") public void deleteBundle(Subject subject, int bundleId) throws Exception { Bundle bundle = this.entityManager.find(Bundle.class, bundleId); if (null == bundle) { return; } checkDeleteBundleAuthz(subject, bundleId); Query q = entityManager.createNamedQuery(BundleVersion.QUERY_FIND_BY_BUNDLE_ID); q.setParameter("bundleId", bundleId); List<BundleVersion> bvs = q.getResultList(); for (BundleVersion bv : bvs) { bundleManager.deleteBundleVersion(subject, bv.getId(), false); entityManager.flush(); } // remove bundle from any assigned bundle groups // wrap in new HashSet to avoid ConcurrentModificationExceptions. Set<BundleGroup> BundleGroupsToRemove = new HashSet<BundleGroup>(bundle.getBundleGroups()); for (BundleGroup bg : BundleGroupsToRemove) { bg.removeBundle(bundle); } // we need to whack the Repo once the Bundle no longer refers to it Repo bundleRepo = bundle.getRepo(); this.entityManager.remove(bundle); this.entityManager.flush(); // delete the repo as overlord, this allows users without MANAGE_INVENTORY permission to delete bundles repoManager.deleteRepo(subjectManager.getOverlord(), bundleRepo.getId()); } @Override public void deleteBundleDeployment(Subject subject, int bundleDeploymentId) throws Exception { BundleDeployment doomed = this.entityManager.find(BundleDeployment.class, bundleDeploymentId); if (null == doomed) { return; } checkDeployBundleAuthz(subject, doomed.getBundleVersion().getBundle().getId(), doomed.getDestination() .getGroup().getId()); // only allow deployments to be deleted if they are not started or finished if (BundleDeploymentStatus.PENDING == doomed.getStatus() || BundleDeploymentStatus.SUCCESS == doomed.getStatus() || BundleDeploymentStatus.FAILURE == doomed.getStatus() || BundleDeploymentStatus.MIXED == doomed.getStatus()) { // change the pointer like in the linked list (ie. when removing B from A -> B -> C, it should result // in following situation: A -> C) Query q = entityManager.createNamedQuery(BundleDeployment.QUERY_UPDATE_FOR_DEPLOYMENT_REMOVE); q.setParameter("bundleId", doomed.getId()); q.executeUpdate(); entityManager.flush(); entityManager.remove(doomed); } else { throw new IllegalArgumentException("Can not delete deployment with status [" + doomed.getStatus() + "]"); } } @Override public void deleteBundleDestination(Subject subject, int destinationId) throws Exception { BundleDestination doomed = this.entityManager.find(BundleDestination.class, destinationId); if (null == doomed) { return; } checkDeployBundleAuthz(subject, doomed.getBundle().getId(), doomed.getGroup().getId()); // deployments replace other deployments and have a self-referring FK. The deployments // need to be removed in a way that will ensure that a replaced deployment is not removed // prior to the replacer. To do this we'll just blanket update all the doomed deployments // to break the FK dependency with nulls. Query q = entityManager.createNamedQuery(BundleDeployment.QUERY_UPDATE_FOR_DESTINATION_REMOVE); q.setParameter("destinationId", destinationId); q.executeUpdate(); entityManager.flush(); entityManager.remove(doomed); } @Override public void deleteBundleVersion(Subject subject, int bundleVersionId, boolean deleteBundleIfEmpty) throws Exception { BundleVersion bundleVersion = this.entityManager.find(BundleVersion.class, bundleVersionId); if (null == bundleVersion) { return; } int bundleId = bundleVersion.getBundle().getId(); checkDeleteBundleAuthz(subject, bundleId); // After we delete this bundle version, this is the version order value that is being removed. // Later we need to re-order the other bundles that are newer than this so their version orders are readjusted. int doomedBundleVersionOrder = bundleVersion.getVersionOrder(); // deployments replace other deployments and have a self-referring FK. The deployments // need to be removed in a way that will ensure that a replaced deployment is not removed // prior to the replacer. To do this we'll just blanket update all the doomed deployments // to break the FK dependency with nulls. Query q = entityManager.createNamedQuery(BundleDeployment.QUERY_UPDATE_FOR_VERSION_REMOVE); q.setParameter("bundleVersionId", bundleVersionId); q.executeUpdate(); entityManager.flush(); // remove the bundle version - cascade remove the deployments which will cascade remove the resource deployments. this.entityManager.remove(bundleVersion); if (deleteBundleIfEmpty) { this.entityManager.flush(); q = entityManager.createNamedQuery(BundleVersion.QUERY_FIND_VERSION_INFO_BY_BUNDLE_ID); q.setParameter("bundleId", bundleId); if (q.getResultList().size() == 0) { // there are no more bundle versions left, blow away the bundle and all repo/bundle files associated with it deleteBundle(subject, bundleId); doomedBundleVersionOrder = -1; // just a marker to let us know not to bother with adjusting version orders } } if (doomedBundleVersionOrder >= 0) { q = entityManager.createNamedQuery(BundleVersion.UPDATE_VERSION_ORDER_BY_BUNDLE_ID_AFTER_DELETE); q.setParameter("bundleId", bundleId); q.setParameter("versionOrder", doomedBundleVersionOrder); q.executeUpdate(); } } private void safeClose(InputStream is) { if (null != is) { try { is.close(); } catch (Exception e) { LOG.warn("Failed to close InputStream", e); } } } @SuppressWarnings("unused") private void safeClose(OutputStream os) { if (null != os) { try { os.close(); } catch (Exception e) { LOG.warn("Failed to close OutputStream", e); } } } @Override public void assignBundlesToBundleGroups(Subject subject, int bundleGroupIds[], int[] bundleIds) { if (null == bundleGroupIds || null == bundleIds) { return; } for (int bundleGroupId : bundleGroupIds) { BundleGroup bundleGroup = entityManager.find(BundleGroup.class, bundleGroupId); if (null == bundleGroup) { throw new IllegalArgumentException("BundleGroup does not exist for bundleGroupId [" + bundleGroupId + "]"); } checkAssignBundleGroupAuthz(subject, bundleGroupId, bundleIds); for (int bundleId : bundleIds) { Bundle bundle = entityManager.find(Bundle.class, bundleId); if (null == bundle) { throw new IllegalArgumentException("Bundle does not exist for bundleId [" + bundleId + "]"); } bundleGroup.addBundle(bundle); } } } @Override @RequiredPermission(Permission.MANAGE_BUNDLE_GROUPS) public BundleGroup createBundleGroup(Subject subject, BundleGroup bundleGroup) throws Exception { String name = bundleGroup.getName(); if (null == name || "".equals(name.trim())) { throw new IllegalArgumentException("Invalid bundleGroupName: " + name); } BundleGroupCriteria c = new BundleGroupCriteria(); c.addFilterName(name); c.setStrict(true); if (!bundleManager.findBundleGroupsByCriteria(subject, c).isEmpty()) { throw new IllegalArgumentException("Invalid bundleGroupName, bundle group already exists with name: " + name); } entityManager.persist(bundleGroup); Set<Bundle> bundles = bundleGroup.getBundles(); if (null != bundles) { int[] bundleIds = new int[bundles.size()]; int i = 0; for (Bundle b : bundles) { bundleIds[i++] = b.getId(); } assignBundlesToBundleGroups(subject, new int[] { bundleGroup.getId() }, bundleIds); } return bundleGroup; } @Override @RequiredPermission(Permission.MANAGE_BUNDLE_GROUPS) public void deleteBundleGroups(Subject subject, int[] bundleGroupIds) throws Exception { for (int bundleGroupId : bundleGroupIds) { BundleGroup bundleGroup = this.entityManager.find(BundleGroup.class, bundleGroupId); if (null == bundleGroup) { return; } // unassign any bundles assigned to the bundle group // wrap in new HashSet to avoid ConcurrentModificationExceptions. Set<Bundle> bundlesToRemove = new HashSet<Bundle>(bundleGroup.getBundles()); for (Bundle b : bundlesToRemove) { bundleGroup.removeBundle(b); } // remove from any roles for (Role r : bundleGroup.getRoles()) { r.removeBundleGroup(bundleGroup); } bundleGroup = entityManager.merge(bundleGroup); // now remove the bundle group entityManager.remove(bundleGroup); } } @Override public PageList<BundleGroup> findBundleGroupsByCriteria(Subject subject, BundleGroupCriteria criteria) { CriteriaQueryGenerator generator = new CriteriaQueryGenerator(subject, criteria); // filter by bundle groups that are viewable if (!authorizationManager.hasGlobalPermission(subject, Permission.MANAGE_BUNDLE_GROUPS)) { generator.setAuthorizationBundleFragment(CriteriaQueryGenerator.AuthorizationTokenType.BUNDLE_GROUP, subject.getId(), null); } CriteriaQueryRunner<BundleGroup> queryRunner = new CriteriaQueryRunner<BundleGroup>(criteria, generator, entityManager); return queryRunner.execute(); } @Override public void unassignBundlesFromBundleGroups(Subject subject, int[] bundleGroupIds, int[] bundleIds) { if (null == bundleGroupIds || null == bundleIds) { return; } for (int bundleGroupId : bundleGroupIds) { BundleGroup bundleGroup = entityManager.find(BundleGroup.class, bundleGroupId); if (null == bundleGroup) { throw new IllegalArgumentException("BundleGroup does not exist for bundleGroupId [" + bundleGroupId + "]"); } checkUnassignBundleGroupAuthz(subject, bundleGroupId, bundleIds); for (int bundleId : bundleIds) { Bundle bundle = entityManager.find(Bundle.class, bundleId); if (null == bundle) { throw new IllegalArgumentException("Bundle does not exist for bundleId [" + bundleId + "]"); } bundleGroup.removeBundle(bundle); } } } /** * <pre> * Required Permissions: Either: * - Global.CREATE_BUNDLES and Global.VIEW_BUNDLES * - Global.CREATE_BUNDLES and BundleGroup.VIEW_BUNDLES_IN_GROUP for bundle group BG * - BundleGroup.CREATE_BUNDLES_IN_GROUP for bundle group BG * </pre> * @param subject * @param bundleGroupIds null or 0 length for unassigned initial bundle version creation * @throws PermissionException */ private void checkCreateInitialBundleVersionAuthz(Subject subject, int[] bundleGroupIds) throws PermissionException { Set<Permission> globalPerms = authorizationManager.getExplicitGlobalPermissions(subject); boolean hasGlobalCreateBundles = globalPerms.contains(Permission.CREATE_BUNDLES); if (hasGlobalCreateBundles && globalPerms.contains(Permission.VIEW_BUNDLES)) { return; } if (null == bundleGroupIds || bundleGroupIds.length == 0) { String msg = "Subject [" + subject.getName() + "] requires Global CREATE_BUNDLES and VIEW_BUNDLES to create unassigned initial bundle version."; throw new PermissionException(msg, new BundleNotFoundException()); // set the cause to BNFE, this is helpful to some callers } for (int bundleGroupId : bundleGroupIds) { boolean authzPassed; if (hasGlobalCreateBundles) { authzPassed = authorizationManager.canViewBundleGroup(subject, bundleGroupId); } else { authzPassed = authorizationManager.hasBundleGroupPermission(subject, Permission.CREATE_BUNDLES_IN_GROUP, bundleGroupId); } if (!authzPassed) { String msg = "Subject [" + subject.getName() + "] requires either Global.CREATE_BUNDLES + BundleGroup.VIEW_BUNDLES_IN_GROUP, or BundleGroup.CREATE_BUNDLES_IN_GROUP, to create or update a bundle in bundle group [" + Arrays.toString(bundleGroupIds) + "]."; throw new PermissionException(msg); } } } /** * <pre> * Required Permissions: Either: * - Global.CREATE_BUNDLES and Global.VIEW_BUNDLES * - Global.CREATE_BUNDLES and BundleGroup.VIEW_BUNDLES_IN_GROUP for bundle group BG and the relevant bundle is assigned to BG * - BundleGroup.CREATE_BUNDLES_IN_GROUP for bundle group BG and the relevant bundle is assigned to BG * </pre> * @param subject * @param bundleId required, bundleId of bundle in which bundle version is being created/updated * @throws PermissionException */ private void checkCreateBundleVersionAuthz(Subject subject, int bundleId) throws PermissionException { if (bundleId <= 0) { throw new IllegalArgumentException( "Must supply valid bundleId for bundle version being created. BundleId specified [" + bundleId + "]"); } Set<Permission> globalPerms = authorizationManager.getExplicitGlobalPermissions(subject); boolean hasGlobalCreateBundles = globalPerms.contains(Permission.CREATE_BUNDLES); if (hasGlobalCreateBundles && globalPerms.contains(Permission.VIEW_BUNDLES)) { return; } if (hasGlobalCreateBundles) { if (authorizationManager.canViewBundle(subject, bundleId)) { return; } } else { if (authorizationManager.hasBundlePermission(subject, Permission.CREATE_BUNDLES_IN_GROUP, bundleId)) { return; } } String msg = "Subject [" + subject.getName() + "] requires either Global.CREATE_BUNDLES + BundleGroup.VIEW_BUNDLES_IN_GROUP, or BundleGroup.CREATE_BUNDLES_IN_GROUP, to create or update a bundleVersion for bundle [" + bundleId + "]."; throw new PermissionException(msg); } /** * <pre> * Requires VIEW permission for the relevant bundle and one of: * - Global.MANAGE_BUNDLE_GROUPS * - Global.CREATE_BUNDLE * - BundleGroup.ASSIGN_BUNDLES_TO_GROUP for the relevant bundle group * - BundleGroup.CREATE_BUNDLES_IN_GROUP for the relevant bundle group * </pre> * @param subject * @param bundleGroupId an existing bundle group * @param bundleIds existing bundles * @throws PermissionException */ private void checkAssignBundleGroupAuthz(Subject subject, int bundleGroupId, int[] bundleIds) throws PermissionException { Set<Permission> globalPerms = authorizationManager.getExplicitGlobalPermissions(subject); boolean hasGlobalManageBundleGroups = globalPerms.contains(Permission.MANAGE_BUNDLE_GROUPS); boolean hasGlobalCreateBundles = globalPerms.contains(Permission.CREATE_BUNDLES); boolean hasGlobalViewBundles = globalPerms.contains(Permission.VIEW_BUNDLES); if ((hasGlobalManageBundleGroups || hasGlobalCreateBundles) && hasGlobalViewBundles) { return; } boolean canAssign = hasGlobalManageBundleGroups || hasGlobalCreateBundles || authorizationManager .hasBundleGroupPermission(subject, Permission.CREATE_BUNDLES_IN_GROUP, bundleGroupId) || authorizationManager .hasBundleGroupPermission(subject, Permission.ASSIGN_BUNDLES_TO_GROUP, bundleGroupId); if (!canAssign) { String msg = "Subject [" + subject.getName() + "] requires one of Global.MANAGE_BUNDLE_GROUPS, Global.CREATE_BUNDLES, BundleGroup.CREATE_BUNDLES_IN_GROUP, or BundleGroup.ASSIGN_BUNDLES_TO_GROUP to assign a bundle to bundle group [" + bundleGroupId + "]."; throw new PermissionException(msg); } for (int bundleId : bundleIds) { if (bundleId <= 0) { throw new IllegalArgumentException("Invalid bundleId: [" + bundleId + "]"); } if (!authorizationManager.canViewBundle(subject, bundleId)) { String msg = "Subject [" + subject.getName() + "] requires either Global.VIEW_BUNDLES or BundleGroup.VIEW_BUNDLES_IN_GROUP to assign bundle [" + bundleId + "] to bundle group [" + bundleGroupId + "]"; throw new PermissionException(msg); } } } /** * <pre> * Requires VIEW permission for the relevant bundles and one of: * - Global.MANAGE_BUNDLE_GROUPS * - Global.DELETE_BUNDLE * - BundleGroup.UNASSIGN_BUNDLES_FROM_GROUP for the relevant bundle group * - BundleGroup.DELETE_BUNDLES_FROM_GROUP for the relevant bundle group * </pre> * * @param subject * @param bundleGroupId an existing bundle group * @param bundleIds existing bundles * @throws PermissionException */ private void checkUnassignBundleGroupAuthz(Subject subject, int bundleGroupId, int[] bundleIds) throws PermissionException { Set<Permission> globalPerms = authorizationManager.getExplicitGlobalPermissions(subject); boolean hasGlobalManageBundleGroups = globalPerms.contains(Permission.MANAGE_BUNDLE_GROUPS); boolean hasGlobalDeleteBundles = globalPerms.contains(Permission.DELETE_BUNDLES); boolean hasGlobalViewBundles = globalPerms.contains(Permission.VIEW_BUNDLES); if ((hasGlobalManageBundleGroups || hasGlobalDeleteBundles) && hasGlobalViewBundles) { return; } boolean canUnassign = hasGlobalManageBundleGroups || hasGlobalDeleteBundles || authorizationManager.hasBundleGroupPermission(subject, Permission.DELETE_BUNDLES_FROM_GROUP, bundleGroupId) || authorizationManager.hasBundleGroupPermission(subject, Permission.UNASSIGN_BUNDLES_FROM_GROUP, bundleGroupId); if (!canUnassign) { String msg = "Subject [" + subject.getName() + "] requires one of Global.MANAGE_BUNDLE_GROUPS, Global.DELETE_BUNDLES, BundleGroup.DELETE_BUNDLES_FROM_GROUP, or BundleGroup.UNASSIGN_BUNDLES_FROM_GROUP to unassign a bundle from bundle group [" + bundleGroupId + "]."; throw new PermissionException(msg); } for (int bundleId : bundleIds) { if (bundleId <= 0) { throw new IllegalArgumentException("Invalid bundleId: [" + bundleId + "]"); } if (!authorizationManager.canViewBundle(subject, bundleId)) { String msg = "Subject [" + subject.getName() + "] requires either Global.VIEW_BUNDLES or BundleGroup.VIEW_BUNDLES_IN_GROUP to unassign bundle [" + bundleId + "] from bundle group [" + bundleGroupId + "]"; throw new PermissionException(msg); } } } /** * <pre> * Required Permissions: Either: * - Global.DEPLOY_BUNDLES and a view of the relevant bundle and a view of the relevant resource group (may involve multiple roles) * - Resource.DEPLOY_BUNDLES_TO_GROUP and a view of the relevant bundle and a view of the relevant resource group (may involve multiple roles) * </pre> */ private void checkDeployBundleAuthz(Subject subject, int bundleId, int resourceGroupId) throws PermissionException { boolean hasResourceGroupView = authorizationManager.canViewGroup(subject, resourceGroupId); if (!hasResourceGroupView) { String msg = "Subject [" + subject.getName() + "] requires VIEW permission on resource group [" + resourceGroupId + "]."; throw new PermissionException(msg); } Set<Permission> globalPerms = authorizationManager.getExplicitGlobalPermissions(subject); boolean hasGlobalDeployBundles = globalPerms.contains(Permission.DEPLOY_BUNDLES); boolean hasGlobalViewBundles = globalPerms.contains(Permission.VIEW_BUNDLES); if (hasGlobalDeployBundles && hasGlobalViewBundles) { return; } boolean hasResourceGroupDeploy = hasGlobalDeployBundles || authorizationManager.hasGroupPermission(subject, Permission.DEPLOY_BUNDLES_TO_GROUP, resourceGroupId); boolean hasBundleView = hasGlobalViewBundles || authorizationManager.canViewBundle(subject, bundleId); if (!(hasResourceGroupDeploy && hasBundleView)) { String msg = "Subject [" + subject.getName() + "] requires DEPLOY permission (global or on for resource group [" + resourceGroupId + "] and VIEW permission for bundle [" + bundleId + "]"; throw new PermissionException(msg); } } /** * <pre> * Required Permissions: Either: * - Global.DELETE_BUNDLES and Global.VIEW_BUNDLES * - Global.DELETE_BUNDLES and BundleGroup.VIEW_BUNDLES_IN_GROUP for bundle group BG and the relevant bundle is assigned to BG * - BundleGroup.DELETE_BUNDLES_FROM_GROUP for bundle group BG and the relevant bundle is assigned to BG * </pre> * @param subject * @param bundleId required, bundleId of bundle, or the bundle version, being deleted * @throws PermissionException */ private void checkDeleteBundleAuthz(Subject subject, int bundleId) throws PermissionException { if (bundleId <= 0) { throw new IllegalArgumentException( "Must supply valid bundleId for bundle version being deleted. BundleId specified [" + bundleId + "]"); } Set<Permission> globalPerms = authorizationManager.getExplicitGlobalPermissions(subject); boolean hasGlobalDeleteBundles = globalPerms.contains(Permission.DELETE_BUNDLES); if (hasGlobalDeleteBundles && globalPerms.contains(Permission.VIEW_BUNDLES)) { return; } if (hasGlobalDeleteBundles) { if (authorizationManager.canViewBundle(subject, bundleId)) { return; } } else { if (authorizationManager.hasBundlePermission(subject, Permission.DELETE_BUNDLES_FROM_GROUP, bundleId)) { return; } } String msg = "Subject [" + subject.getName() + "] requires either Global.DELETE_BUNDLES + BundleGroup.VIEW_BUNDLES_IN_GROUP, or BundleGroup.DELETE_BUNDLES_FROM_GROUP, to delete bundle [" + bundleId + "]."; throw new PermissionException(msg); } @Override public BundleGroup updateBundleGroup(Subject subject, BundleGroup bundleGroup) throws Exception { BundleGroup attachedBundleGroup = entityManager.find(BundleGroup.class, bundleGroup.getId()); if (attachedBundleGroup == null) { throw new IllegalArgumentException("Cannot update " + bundleGroup + ", because no bundle group exists with id [" + bundleGroup.getId() + "]."); } // First update the simple fields and the permissions. attachedBundleGroup.setName(bundleGroup.getName()); attachedBundleGroup.setDescription(bundleGroup.getDescription()); Set<Bundle> newBundles = bundleGroup.getBundles(); if (newBundles != null) { // wrap in new HashSet to avoid ConcurrentModificationExceptions. Set<Bundle> currentBundles = attachedBundleGroup.getBundles(); Set<Bundle> BundlesToRemove = new HashSet<Bundle>(currentBundles); for (Bundle bg : newBundles) { BundlesToRemove.remove(bg); } for (Bundle bg : BundlesToRemove) { attachedBundleGroup.removeBundle(bg); } for (Bundle bg : newBundles) { Bundle attachedBundle = entityManager.find(Bundle.class, bg.getId()); attachedBundleGroup.addBundle(attachedBundle); } } // Fetch the lazy Set for the return attachedBundleGroup.getBundles().size(); return attachedBundleGroup; } @Override @TransactionAttribute(TransactionAttributeType.NEVER) public BundleVersion createInitialBundleVersionViaToken(Subject subject, int[] bundleGroupIds, String token) throws Exception { File distributionFile = new File(System.getProperty("jboss.server.temp.dir"), token); if (!distributionFile.isFile()) { throw new IllegalArgumentException("Token did not result in valid file [" + distributionFile.getAbsolutePath() + "]"); } return createInitialBundleVersionViaFile(subject, bundleGroupIds, distributionFile); } @Override public BundleGroupAssignmentComposite getAssignableBundleGroups(Subject subject, Subject assigningSubject, int bundleId) throws Exception { Bundle bundle = null; if (0 != bundleId) { bundle = entityManager.find(Bundle.class, bundleId); if (null == bundle) { throw new BundleNotFoundException("Bundle ID [" + bundleId + "]"); } } BundleGroupAssignmentComposite result = new BundleGroupAssignmentComposite(assigningSubject, bundle); Set<Permission> globalPermissions = authorizationManager.getExplicitGlobalPermissions(assigningSubject); boolean hasManageBundleGroups = globalPermissions.contains(Permission.MANAGE_BUNDLE_GROUPS); // can assign any bundle anywhere, or leave unassigned if (hasManageBundleGroups) { BundleGroupCriteria criteria = new BundleGroupCriteria(); // just get all the bundle groups by using overlord and no filters List<BundleGroup> bundleGroups = findBundleGroupsByCriteria(subjectManager.getOverlord(), criteria); result.setCanBeUnassigned(true); result.setBundleGroupMap(populateBundleGroupMap(bundleGroups, bundle)); return result; } boolean hasViewBundles = globalPermissions.contains(Permission.VIEW_BUNDLES); boolean hasCreateBundles = globalPermissions.contains(Permission.CREATE_BUNDLES); boolean isNewBundle = (null == bundle); ArrayList<Permission> permFilter = new ArrayList<Permission>(1); if (isNewBundle) { // set whether can leave unassigned result.setCanBeUnassigned(hasCreateBundles && hasViewBundles); // can assign to bundle groups for which he has create_bundles_in_group permFilter.add(Permission.CREATE_BUNDLES_IN_GROUP); } else { // if necessary, make sure the bundle is viewable if (!hasViewBundles && !authorizationManager.canViewBundle(assigningSubject, bundleId)) { throw new PermissionException("Bundle ID [" + bundleId + "] is not viewable by subject [" + assigningSubject.getName() + "]"); } // can assign to bundle groups for which he has create_bundles_in_group or assign_bundles_to_group permFilter.add(Permission.CREATE_BUNDLES_IN_GROUP); permFilter.add(Permission.ASSIGN_BUNDLES_TO_GROUP); } List<BundleGroup> bundleGroups; if (hasCreateBundles) { // can assign to any viewable bundle group // get all the viewable bundle groups for the subject, no filters BundleGroupCriteria criteria = new BundleGroupCriteria(); bundleGroups = findBundleGroupsByCriteria(assigningSubject, criteria); } else { // can only assign to bundle groups for which he has the necessary permissions RoleCriteria criteria = new RoleCriteria(); criteria.addFilterSubjectId(assigningSubject.getId()); criteria.addFilterPermissions(permFilter); criteria.fetchBundleGroups(true); List<Role> roles = LookupUtil.getRoleManager().findRolesByCriteria(subjectManager.getOverlord(), criteria); bundleGroups = new ArrayList<BundleGroup>(); for (Role role : roles) { for (BundleGroup bundleGroup : role.getBundleGroups()) { if (!bundleGroups.contains(bundleGroup)) { bundleGroups.add(bundleGroup); } } } } result.setBundleGroupMap(populateBundleGroupMap(bundleGroups, bundle)); return result; } private Map<BundleGroup, Boolean> populateBundleGroupMap(List<BundleGroup> bundleGroups, Bundle bundle) { Map<BundleGroup, Boolean> result = new HashMap<BundleGroup, Boolean>(bundleGroups.size()); for (BundleGroup bundleGroup : bundleGroups) { Boolean assigned = ((null != bundle) && bundle.getBundleGroups().contains(bundleGroup)) ? Boolean.TRUE : Boolean.FALSE; result.put(bundleGroup, assigned); } return result; } @Override public void deleteMetadata(Subject subject, ResourceType resourceType) throws Exception { // disconnect from explicitly targeting bundle types for (BundleType bundleType : resourceType.getExplicitlyTargetingBundleTypes()) { bundleType.getExplicitlyTargetedResourceTypes().remove(resourceType); entityManager.merge(bundleType); } resourceType.getExplicitlyTargetingBundleTypes().clear(); // if our resourceType has associated bundleType, sadly we have to delete all bundles of that type if (resourceType.getBundleType() != null) { BundleType toRemove = resourceType.getBundleType(); BundleCriteria criteria = new BundleCriteria(); criteria.addFilterBundleTypeId(toRemove.getId()); for (Bundle bundle : bundleManager.findBundlesByCriteria(subject, criteria)) { deleteBundle(subject, bundle.getId()); } entityManager.remove(toRemove); } } }