/*
* RHQ Management Platform
* Copyright (C) 2005-2013 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.content;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.sql.Blob;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
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.FlushModeType;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.sql.DataSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.ejb3.annotation.TransactionTimeout;
import org.rhq.core.clientapi.agent.PluginContainerException;
import org.rhq.core.clientapi.agent.content.ContentAgentService;
import org.rhq.core.clientapi.server.content.ContentDiscoveryReport;
import org.rhq.core.clientapi.server.content.ContentServiceResponse;
import org.rhq.core.clientapi.server.content.DeletePackagesRequest;
import org.rhq.core.clientapi.server.content.DeployPackagesRequest;
import org.rhq.core.clientapi.server.content.RetrievePackageBitsRequest;
import org.rhq.core.db.DatabaseTypeFactory;
import org.rhq.core.db.DbUtil;
import org.rhq.core.domain.auth.Subject;
import org.rhq.core.domain.authz.Permission;
import org.rhq.core.domain.configuration.Configuration;
import org.rhq.core.domain.content.Architecture;
import org.rhq.core.domain.content.ContentRequestStatus;
import org.rhq.core.domain.content.ContentRequestType;
import org.rhq.core.domain.content.ContentServiceRequest;
import org.rhq.core.domain.content.InstalledPackage;
import org.rhq.core.domain.content.InstalledPackageHistory;
import org.rhq.core.domain.content.InstalledPackageHistoryStatus;
import org.rhq.core.domain.content.Package;
import org.rhq.core.domain.content.PackageBits;
import org.rhq.core.domain.content.PackageBitsBlob;
import org.rhq.core.domain.content.PackageCategory;
import org.rhq.core.domain.content.PackageDetailsKey;
import org.rhq.core.domain.content.PackageInstallationStep;
import org.rhq.core.domain.content.PackageType;
import org.rhq.core.domain.content.PackageVersion;
import org.rhq.core.domain.content.PackageVersionFormatDescription;
import org.rhq.core.domain.content.ValidatablePackageDetailsKey;
import org.rhq.core.domain.content.composite.PackageAndLatestVersionComposite;
import org.rhq.core.domain.content.composite.PackageTypeAndVersionFormatComposite;
import org.rhq.core.domain.content.transfer.ContentResponseResult;
import org.rhq.core.domain.content.transfer.DeployIndividualPackageResponse;
import org.rhq.core.domain.content.transfer.DeployPackageStep;
import org.rhq.core.domain.content.transfer.DeployPackagesResponse;
import org.rhq.core.domain.content.transfer.RemoveIndividualPackageResponse;
import org.rhq.core.domain.content.transfer.RemovePackagesResponse;
import org.rhq.core.domain.content.transfer.ResourcePackageDetails;
import org.rhq.core.domain.criteria.InstalledPackageCriteria;
import org.rhq.core.domain.criteria.PackageCriteria;
import org.rhq.core.domain.criteria.PackageVersionCriteria;
import org.rhq.core.domain.criteria.ResourceCriteria;
import org.rhq.core.domain.resource.Agent;
import org.rhq.core.domain.resource.InvalidPackageTypeException;
import org.rhq.core.domain.resource.Resource;
import org.rhq.core.domain.resource.ResourceCategory;
import org.rhq.core.domain.resource.ResourceCreationDataType;
import org.rhq.core.domain.resource.ResourceType;
import org.rhq.core.domain.util.PageList;
import org.rhq.core.util.MessageDigestGenerator;
import org.rhq.core.util.collection.ArrayUtils;
import org.rhq.core.util.exception.ThrowableUtil;
import org.rhq.core.util.jdbc.JDBCUtil;
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.core.AgentManagerLocal;
import org.rhq.enterprise.server.plugin.pc.content.PackageDetailsValidationException;
import org.rhq.enterprise.server.plugin.pc.content.PackageTypeBehavior;
import org.rhq.enterprise.server.resource.ResourceManagerLocal;
import org.rhq.enterprise.server.resource.ResourceTypeManagerLocal;
import org.rhq.enterprise.server.resource.ResourceTypeNotFoundException;
import org.rhq.enterprise.server.scheduler.jobs.DataPurgeJob;
import org.rhq.enterprise.server.util.CriteriaQueryGenerator;
import org.rhq.enterprise.server.util.CriteriaQueryRunner;
/**
* EJB that handles content subsystem interaction with resources, including content discovery reports and create/delete
* functionality.
*
* @author Jason Dobies
*/
@Stateless
public class ContentManagerBean implements ContentManagerLocal, ContentManagerRemote {
private static final Log LOG = LogFactory.getLog(ContentManagerBean.class);
// Constants --------------------------------------------
/**
* Amount of time a request may be outstanding against an agent before it is marked as timed out.
*/
private static final int REQUEST_TIMEOUT = 1000 * 60 * 60;
private static final String TMP_FILE_PREFIX = "rhq-content-";
private static final String TMP_FILE_SUFFIX = ".bin";
@PersistenceContext(unitName = RHQConstants.PERSISTENCE_UNIT_NAME)
private EntityManager entityManager;
@javax.annotation.Resource(name = "RHQ_DS", mappedName = RHQConstants.DATASOURCE_JNDI_NAME)
private DataSource dataSource;
@EJB
private AgentManagerLocal agentManager;
@EJB
private AuthorizationManagerLocal authorizationManager;
@EJB
private ContentManagerLocal contentManager;
@EJB
private ResourceManagerLocal resourceManager;
@EJB
private ResourceTypeManagerLocal resourceTypeManager;
@EJB
private RepoManagerLocal repoManager;
@EJB
private SubjectManagerLocal subjectManager;
// ContentManagerLocal Implementation --------------------------------------------
@Override
@TransactionAttribute(TransactionAttributeType.NEVER)
public void mergeDiscoveredPackages(ContentDiscoveryReport report) {
int resourceId = report.getResourceId();
// For performance tracking
long start = System.currentTimeMillis();
if (LOG.isDebugEnabled()) {
LOG.debug("Merging [" + report.getDeployedPackages().size() + "] packages for Resource with id ["
+ resourceId + "]...");
}
// Load the resource and its installed packages.
ResourceCriteria c = new ResourceCriteria();
c.addFilterId(resourceId);
c.fetchInstalledPackages(true);
List<Resource> result = resourceManager.findResourcesByCriteria(subjectManager.getOverlord(), c);
if (result.isEmpty()) {
LOG.error("Invalid resource ID specified for merge. Resource ID: " + resourceId);
return;
}
// Installed packages on the Resource that are not referenced in the report will be removed. The doomed
// list is seeded with all of the currently installed packages, and what remains after processing are
// those to be removed.
Resource resource = result.get(0);
Set<InstalledPackage> doomedPackages = new HashSet<InstalledPackage>(resource.getInstalledPackages());
// Timestamp to use for all audit trail entries from this report
long timestamp = System.currentTimeMillis();
// The report contains an entire snapshot of packages, each has to be represented as an InstalledPackage
for (ResourcePackageDetails discoveredPackage : report.getDeployedPackages()) {
// process each in a separate Tx to avoid a large umbrella Tx and locking issues.
contentManager.handleDiscoveredPackage(resource, discoveredPackage, doomedPackages, timestamp);
} // end resource package loop
// Now remove from the resource the remaining doomed installed packages, they were no longer discovered
contentManager.removeInstalledPackages(resource, doomedPackages, timestamp);
if (LOG.isDebugEnabled()) {
LOG.debug("Finished merging [" + report.getDeployedPackages().size() + "] packages in "
+ (System.currentTimeMillis() - start) + "ms");
}
}
@Override
public void handleDiscoveredPackage(Resource resource, ResourcePackageDetails discoveredPackage,
Set<InstalledPackage> doomedPackages, long timestamp) {
Package generalPackage = null;
PackageVersion packageVersion = null;
// Load the overall package (used in a few places later in this loop)
Query packageQuery = entityManager.createNamedQuery(Package.QUERY_FIND_BY_NAME_PKG_TYPE_RESOURCE_TYPE);
packageQuery.setFlushMode(FlushModeType.COMMIT);
// these form a query for a unique package
packageQuery.setParameter("name", discoveredPackage.getName());
packageQuery.setParameter("packageTypeName", discoveredPackage.getPackageTypeName());
packageQuery.setParameter("resourceTypeId", resource.getResourceType().getId());
List<Package> resultPackages = packageQuery.getResultList();
if (resultPackages.size() > 0) {
generalPackage = resultPackages.get(0); // returns at most 1 Package
}
// If the package exists see if package version already exists
if (null != generalPackage) {
Query packageVersionQuery = entityManager.createNamedQuery(PackageVersion.QUERY_FIND_BY_PACKAGE_VERSION);
packageVersionQuery.setFlushMode(FlushModeType.COMMIT);
packageVersionQuery.setParameter("packageId", generalPackage.getId());
packageVersionQuery.setParameter("version", discoveredPackage.getVersion());
List<PackageVersion> resultPackageVersions = packageVersionQuery.getResultList();
// Although the PV unique index is (package,version,arch) in reality the architecture portion is
// superfluous. The version is now basically unique (it's basically an enhanced SHA) so it means that
// two different architectures would basically have two different versions anyway. So, despite the
// DB model, this query will return at most 1 PV.
if (resultPackageVersions.size() > 0) {
packageVersion = resultPackageVersions.get(0); // returns at most 1 PackageVersion
}
}
// If we didn't find a package version for this deployed package, we will need to create it
if (null == packageVersion) {
if (null == generalPackage) {
Query packageTypeQuery = entityManager
.createNamedQuery(PackageType.QUERY_FIND_BY_RESOURCE_TYPE_ID_AND_NAME);
packageTypeQuery.setFlushMode(FlushModeType.COMMIT);
packageTypeQuery.setParameter("typeId", resource.getResourceType().getId());
packageTypeQuery.setParameter("name", discoveredPackage.getPackageTypeName());
PackageType packageType = (PackageType) packageTypeQuery.getSingleResult();
generalPackage = new Package(discoveredPackage.getName(), packageType);
generalPackage = persistOrMergePackageSafely(generalPackage);
}
// Create a new package version and attach to the general package
Architecture packageArchitecture;
Query architectureQuery = entityManager.createNamedQuery(Architecture.QUERY_FIND_BY_NAME);
architectureQuery.setFlushMode(FlushModeType.COMMIT);
architectureQuery.setParameter("name", discoveredPackage.getArchitectureName());
// We don't have an architecture enum, so it's very possible the plugin will pass in a crap string here.
// If the architecture is unknown just use "noarch" and log a warning. We don't want to blow up
// just because a plugin didn't set this correctly, as architecture is nearly useless at this point.
try {
packageArchitecture = (Architecture) architectureQuery.getSingleResult();
} catch (Exception e) {
LOG.warn("Discovered Architecture [" + discoveredPackage.getArchitectureName()
+ "] not found for package [" + discoveredPackage.getName()
+ "]. Setting to [noarch] and continuing...");
packageArchitecture = getNoArchitecture();
}
packageVersion = new PackageVersion(generalPackage, discoveredPackage.getVersion(), packageArchitecture);
packageVersion.setDisplayName(discoveredPackage.getDisplayName());
packageVersion.setDisplayVersion(discoveredPackage.getDisplayVersion());
packageVersion.setFileCreatedDate(discoveredPackage.getFileCreatedDate());
packageVersion.setFileName(discoveredPackage.getFileName());
packageVersion.setFileSize(discoveredPackage.getFileSize());
packageVersion.setLicenseName(discoveredPackage.getLicenseName());
packageVersion.setLicenseVersion(discoveredPackage.getLicenseVersion());
packageVersion.setLongDescription(discoveredPackage.getLongDescription());
packageVersion.setMD5(discoveredPackage.getMD5());
packageVersion.setMetadata(discoveredPackage.getMetadata());
packageVersion.setSHA256(discoveredPackage.getSHA256());
packageVersion.setShortDescription(discoveredPackage.getShortDescription());
packageVersion.setExtraProperties(discoveredPackage.getExtraProperties());
packageVersion = persistOrMergePackageVersionSafely(packageVersion);
} else {
// At this point we know a PackageVersion existed previously in the DB already. If it is already
// installed to the resource then we are done, and we can remove it from the doomed package list.
for (Iterator<InstalledPackage> i = doomedPackages.iterator(); i.hasNext();) {
InstalledPackage ip = i.next();
PackageVersion pv = ip.getPackageVersion();
if (pv.getId() == packageVersion.getId()) {
i.remove();
return;
}
}
}
// At this point, we have the package and package version in the system (now added if they weren't already)
// We've also punched out early if we already knew about the installed package, so we won't add another
// reference from the resource to the package nor another audit trail entry saying it was discovered.
// Create a new installed package entry in the audit
InstalledPackage newlyInstalledPackage = new InstalledPackage();
newlyInstalledPackage.setPackageVersion(packageVersion);
newlyInstalledPackage.setResource(resource);
newlyInstalledPackage.setInstallationDate(discoveredPackage.getInstallationTimestamp());
entityManager.persist(newlyInstalledPackage);
// Create an audit trail entry to show how this package was added to the system
InstalledPackageHistory history = new InstalledPackageHistory();
history.setDeploymentConfigurationValues(discoveredPackage.getDeploymentTimeConfiguration());
history.setPackageVersion(packageVersion);
history.setResource(resource);
history.setStatus(InstalledPackageHistoryStatus.DISCOVERED);
history.setTimestamp(timestamp);
entityManager.persist(history);
}
@Override
public void removeInstalledPackages(Resource resource, Set<InstalledPackage> doomedPackages, long timestamp) {
// first, create history records to audit that the previously discovered package was no longer found
for (InstalledPackage doomedPackage : doomedPackages) {
InstalledPackageHistory history = new InstalledPackageHistory();
history.setPackageVersion(doomedPackage.getPackageVersion());
history.setResource(resource);
history.setStatus(InstalledPackageHistoryStatus.MISSING);
history.setTimestamp(timestamp);
entityManager.persist(history);
}
// let's flush these to not buffer too much
entityManager.flush();
// now, remove the installed packages (in batches to protect against oracle limit)
Query query = entityManager.createNamedQuery(InstalledPackage.QUERY_DELETE_BY_IDS);
final int batchSize = 200;
List<Integer> doomedIds = new ArrayList(doomedPackages.size());
for (InstalledPackage ip : doomedPackages) {
doomedIds.add(ip.getId());
}
while (!doomedIds.isEmpty()) {
int size = doomedIds.size();
int end = (batchSize < size) ? batchSize : size;
List<Integer> idBatch = doomedIds.subList(0, end);
query.setParameter("ids", idBatch);
query.executeUpdate();
// Advance our progress and possibly help GC. This will remove the processed ids from the backing list
idBatch.clear();
}
}
@Override
public void deployPackages(Subject user, int[] resourceIds, int[] packageVersionIds) {
this.deployPackagesWithNote(user, resourceIds, packageVersionIds, null);
}
@Override
public void deployPackagesWithNote(Subject user, int[] resourceIds, int[] packageVersionIds, String requestNotes) {
for (int resourceId : resourceIds) {
Set<ResourcePackageDetails> packages = new HashSet<ResourcePackageDetails>();
for (int packageVersionId : packageVersionIds) {
PackageVersion packageVersion = entityManager.find(PackageVersion.class, packageVersionId);
if (packageVersion == null) {
throw new IllegalArgumentException("PackageVersion: [" + packageVersionId + "] not found!");
}
ResourcePackageDetails details = ContentManagerHelper.packageVersionToDetails(packageVersion);
details.setInstallationTimestamp(System.currentTimeMillis());
packages.add(details);
}
deployPackages(user, resourceId, packages, requestNotes);
}
}
@Override
public void deployPackages(Subject user, int resourceId, Set<ResourcePackageDetails> packages, String requestNotes) {
if (packages == null) {
throw new IllegalArgumentException("packages cannot be null");
}
LOG.info("Deploying " + packages.size() + " packages on resource ID [" + resourceId + "]");
if (packages.size() == 0) {
return;
}
// Check permissions first
if (!authorizationManager.hasResourcePermission(user, Permission.MANAGE_CONTENT, resourceId)) {
throw new PermissionException("User [" + user.getName()
+ "] does not have permission to deploy packages for resource ID [" + resourceId + "]");
}
// Load entities for references later in the method
Resource resource = entityManager.find(Resource.class, resourceId);
Agent agent = resource.getAgent();
// Persist in separate transaction so it is committed immediately, before the request is sent to the agent
// This call will also create the audit trail entry.
ContentServiceRequest persistedRequest = contentManager.createDeployRequest(resourceId, user.getName(),
packages, requestNotes);
// Package into transfer object
DeployPackagesRequest transferRequest = new DeployPackagesRequest(persistedRequest.getId(), resourceId,
packages);
// Make call to agent
try {
AgentClient agentClient = agentManager.getAgentClient(agent);
ContentAgentService agentService = agentClient.getContentAgentService();
agentService.deployPackages(transferRequest);
} catch (RuntimeException e) {
LOG.error("Error while sending deploy request to agent", e);
// Update the request with the failure
contentManager.failRequest(persistedRequest.getId(), e);
// Throw so caller knows an error happened
throw e;
}
}
@Override
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public ContentServiceRequest createDeployRequest(int resourceId, String username,
Set<ResourcePackageDetails> packages, String notes) {
Resource resource = entityManager.find(Resource.class, resourceId);
ContentServiceRequest persistedRequest = new ContentServiceRequest(resource, username,
ContentRequestType.DEPLOY);
persistedRequest.setStatus(ContentRequestStatus.IN_PROGRESS);
persistedRequest.setNotes(notes);
long timestamp = System.currentTimeMillis();
for (ResourcePackageDetails packageDetails : packages) {
// Load the package version for the relationship
PackageDetailsKey key = packageDetails.getKey();
Query packageVersionQuery = entityManager
.createNamedQuery(
PackageVersion.QUERY_FIND_BY_PACKAGE_DETAILS_KEY_WITH_NON_NULL_RESOURCE_TYPE, PackageVersion.class);
packageVersionQuery.setParameter("packageName", key.getName());
packageVersionQuery.setParameter("packageTypeName", key.getPackageTypeName());
packageVersionQuery.setParameter("architectureName", key.getArchitectureName());
packageVersionQuery.setParameter("version", key.getVersion());
packageVersionQuery.setParameter("resourceTypeId", resource.getResourceType().getId());
@SuppressWarnings("unchecked")
List<PackageVersion> packageVersions = packageVersionQuery.getResultList();
if (packageVersions.isEmpty()) {
ResourceType type = resource.getResourceType();
StringBuilder supportedTypes = new StringBuilder("[");
for (PackageType pt : resource.getResourceType().getPackageTypes()) {
supportedTypes.append(pt.getDisplayName() + " [" + type.getName() + ":" + type.getPlugin() + "],");
}
supportedTypes.deleteCharAt(supportedTypes.length() - 1);
supportedTypes.append("]");
throw new InvalidPackageTypeException(key.getPackageTypeName(), type, supportedTypes.toString());
}
PackageVersion packageVersion = packageVersions.get(0);
// Create the history entity
InstalledPackageHistory history = new InstalledPackageHistory();
history.setContentServiceRequest(persistedRequest);
history.setDeploymentConfigurationValues(packageDetails.getDeploymentTimeConfiguration());
history.setPackageVersion(packageVersion);
history.setResource(resource);
history.setStatus(InstalledPackageHistoryStatus.BEING_INSTALLED);
history.setTimestamp(timestamp);
persistedRequest.addInstalledPackageHistory(history);
}
entityManager.persist(persistedRequest);
return persistedRequest;
}
@Override
@SuppressWarnings("unchecked")
public void completeDeployPackageRequest(DeployPackagesResponse response) {
LOG.info("Completing deploy package response: " + response);
// Load persisted request
Query query = entityManager.createNamedQuery(ContentServiceRequest.QUERY_FIND_BY_ID);
query.setParameter("id", response.getRequestId());
ContentServiceRequest persistedRequest = (ContentServiceRequest) query.getSingleResult();
Resource resource = persistedRequest.getResource();
int resourceTypeId = persistedRequest.getResource().getResourceType().getId();
// Update the persisted request
persistedRequest.setErrorMessage(response.getOverallRequestErrorMessage());
persistedRequest.setStatus(translateRequestResultStatus(response.getOverallRequestResult()));
// All history entries on the request at this point should be considered "in progress". We need to make
// sure each of these is closed out in some capacity. Typically, this will be done by the response
// explicitly indicating the result of each individual package. However, we can't rely on the plugin
// always doing this, so we need to keep track of which ones are not closed to prevent dangling in progress
// entries.
Set<InstalledPackageHistory> requestInProgressEntries = persistedRequest.getInstalledPackageHistory();
// Convert to a map so we can easily remove entries from it as they are closed by the individual
// package responses.
Map<PackageVersion, InstalledPackageHistory> inProgressEntries = new HashMap<PackageVersion, InstalledPackageHistory>(
requestInProgressEntries.size());
for (InstalledPackageHistory history : requestInProgressEntries) {
inProgressEntries.put(history.getPackageVersion(), history);
}
// Handle each individual package
long timestamp = System.currentTimeMillis();
for (DeployIndividualPackageResponse singleResponse : response.getPackageResponses()) {
// Load the package version for the relationship
PackageDetailsKey key = singleResponse.getKey();
Query packageVersionQuery = entityManager
.createNamedQuery(PackageVersion.QUERY_FIND_BY_PACKAGE_DETAILS_KEY_WITH_NON_NULL_RESOURCE_TYPE);
packageVersionQuery.setParameter("packageName", key.getName());
packageVersionQuery.setParameter("packageTypeName", key.getPackageTypeName());
packageVersionQuery.setParameter("architectureName", key.getArchitectureName());
packageVersionQuery.setParameter("version", key.getVersion());
packageVersionQuery.setParameter("resourceTypeId", resourceTypeId);
PackageVersion packageVersion = (PackageVersion) packageVersionQuery.getSingleResult();
// Create the history entity
InstalledPackageHistory history = new InstalledPackageHistory();
history.setContentServiceRequest(persistedRequest);
history.setPackageVersion(packageVersion);
history.setResource(resource);
history.setTimestamp(timestamp);
// Link the deployment configuration values that were saved for the initial history entity for this
// package with this entity as well. This will let us show the user the configuration values on the
// last entry in the audit trail (i.e. for a failed package, the configuration values will be accessible).
Query deploymentConfigurationQuery = entityManager
.createNamedQuery(InstalledPackageHistory.QUERY_FIND_CONFIG_BY_PACKAGE_VERSION_AND_REQ);
deploymentConfigurationQuery.setParameter("packageVersion", packageVersion);
deploymentConfigurationQuery.setParameter("contentServiceRequest", persistedRequest);
deploymentConfigurationQuery.setMaxResults(1);
Configuration deploymentConfiguration = null;
List deploymentConfigurationResults = deploymentConfigurationQuery.getResultList();
if (deploymentConfigurationResults.size() > 0) {
deploymentConfiguration = (Configuration) deploymentConfigurationResults.get(0);
deploymentConfiguration = deploymentConfiguration.deepCopy(false);
}
history.setDeploymentConfigurationValues(deploymentConfiguration);
// If the package indicated installation steps, link them to the resulting history entry
List<DeployPackageStep> transferObjectSteps = singleResponse.getDeploymentSteps();
if (transferObjectSteps != null) {
List<PackageInstallationStep> installationSteps = translateInstallationSteps(transferObjectSteps,
history);
history.setInstallationSteps(installationSteps);
}
if (singleResponse.getResult() == ContentResponseResult.SUCCESS) {
history.setStatus(InstalledPackageHistoryStatus.INSTALLED);
} else {
history.setStatus(InstalledPackageHistoryStatus.FAILED);
history.setErrorMessage(singleResponse.getErrorMessage());
}
entityManager.persist(history);
persistedRequest.addInstalledPackageHistory(history);
// We're closing out the package request for this package version, so remove it from the cache of entries
// that need to be closed
inProgressEntries.remove(packageVersion);
}
// For any entries that were not closed, add closing entries
for (InstalledPackageHistory unclosed : inProgressEntries.values()) {
PackageVersion packageVersion = unclosed.getPackageVersion();
// Create the history entity
InstalledPackageHistory history = new InstalledPackageHistory();
history.setContentServiceRequest(persistedRequest);
history.setPackageVersion(packageVersion);
history.setResource(resource);
history.setTimestamp(timestamp);
// One option is to create a new status that indicates unknown. For now, just give them the same result
// as the overall request result
if (response.getOverallRequestResult() == ContentResponseResult.SUCCESS) {
history.setStatus(InstalledPackageHistoryStatus.INSTALLED);
} else {
history.setStatus(InstalledPackageHistoryStatus.FAILED);
history.setErrorMessage(response.getOverallRequestErrorMessage());
}
entityManager.persist(history);
persistedRequest.addInstalledPackageHistory(history);
}
}
@Override
@TransactionAttribute(TransactionAttributeType.NEVER)
public void removeHistoryDeploymentsBits(){
List<String> resourceTypes = Arrays.asList("Deployment", "DomainDeployment");
List<String> plugins = Arrays.asList("JBossAS7", "EAP7");
Query query = entityManager.createQuery("SELECT pk FROM Package pk WHERE pk.packageType IN ( SELECT pt.id " +
"FROM PackageType pt WHERE pt.category = :packageTypeCategory AND pt.resourceType IN ( SELECT rt.id " +
"FROM ResourceType rt WHERE rt.name IN ( :resourceTypes) AND rt.category = :resourceTypeCategory AND " +
"(rt.plugin IN (:plugins) ) ) )");
query.setParameter("resourceTypes", resourceTypes);
query.setParameter("resourceTypeCategory", ResourceCategory.SERVICE);
query.setParameter("packageTypeCategory", PackageCategory.DEPLOYABLE);
query.setParameter("plugins", plugins);
List <Package> packages = query.getResultList();
for(Package pkg: packages) {
List<Integer> needUnlink = contentManager.purgePackageBits(pkg.getId());
if (DatabaseTypeFactory.isPostgres(DatabaseTypeFactory.getDefaultDatabaseType())) {
for(Integer bitId: needUnlink){
contentManager.unlinkBlob(bitId);
}
}
}
contentManager.removeOrphanedPackageBits();
}
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void removeOrphanedPackageBits(){
Query deleteBitsQuery = entityManager.createNamedQuery(PackageBits.DELETE_IF_NO_PACKAGE_VERSION);
deleteBitsQuery.executeUpdate();
}
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void unlinkBlob(Integer bitsId) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = dataSource.getConnection();
ps = conn.prepareStatement("SELECT BITS FROM " + PackageBits.TABLE_NAME + " WHERE ID = " + bitsId);
rs = ps.executeQuery();
while (rs.next()) {
int blobId = rs.getInt(1);
Statement unlinkStatement = conn.createStatement();
String unlinkSQLProto = "SELECT lo_unlink(%s)";
String sqlUnlink = String.format(unlinkSQLProto, blobId);
unlinkStatement.execute(sqlUnlink);
JDBCUtil.safeClose(unlinkStatement);
}
} catch (SQLException e) {
LOG.warn("Failed to clean package bits with ID " + bitsId);
} finally {
JDBCUtil.safeClose(conn, ps, rs);
}
}
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public List<Integer> purgePackageBits(int packageId){
// Cleaning package bits
final int MAX_HISTORICAL_VERSIONS_PER_PACKAGE = 1;
Query packageVersionQuery = entityManager
.createNamedQuery(PackageVersion.QUERY_FIND_PACKAGE_HISTORICAL_VERSIONS);
ArrayList<Integer> needUnlinking = new ArrayList<Integer>();
packageVersionQuery.setParameter("packageId",packageId);
List<PackageVersion> versions = packageVersionQuery.getResultList();
if(versions.size()>MAX_HISTORICAL_VERSIONS_PER_PACKAGE) {
/* Remove recent packages from the list*/
for (int i = 0; i < MAX_HISTORICAL_VERSIONS_PER_PACKAGE; ++i){
versions.remove(0);
}
/* Set to null all other versions */
for (PackageVersion pv:versions) {
needUnlinking.add(pv.getPackageBits().getId());
pv.setPackageBits(null);
entityManager.merge(pv);
}
}
return needUnlinking;
}
@Override
public void deletePackages(Subject user, int[] resourceIds, int[] installedPackageIds) {
for (int resourceId : resourceIds) {
deletePackages(user, resourceId, installedPackageIds, null);
}
}
@Override
@SuppressWarnings("unchecked")
public void deletePackages(Subject user, int resourceId, int[] installedPackageIds, String requestNotes) {
if (installedPackageIds == null) {
throw new IllegalArgumentException("installedPackages cannot be null");
}
LOG.info("Deleting " + installedPackageIds.length + " from resource ID [" + resourceId + "]");
if (installedPackageIds.length == 0) {
return;
}
// Check permissions first
if (!authorizationManager.hasResourcePermission(user, Permission.MANAGE_CONTENT, resourceId)) {
throw new PermissionException("User [" + user.getName()
+ "] does not have permission to delete installedPackageIds from resource ID [" + resourceId + "]");
}
// Load entities for references later in the method
Resource resource = entityManager.find(Resource.class, resourceId);
Agent agent = resource.getAgent();
// Persist in separate transaction so it is committed immediately, before the request is sent to the agent
// This will also create the audit trail entry
ContentServiceRequest persistedRequest = contentManager.createRemoveRequest(resourceId, user.getName(),
installedPackageIds, requestNotes);
// Package into transfer object
Query query = entityManager.createNamedQuery(InstalledPackage.QUERY_FIND_BY_SET_OF_IDS);
query.setParameter("packageIds", ArrayUtils.wrapInList(installedPackageIds));
List<InstalledPackage> installedPackageList = query.getResultList();
Set<ResourcePackageDetails> transferPackages = new HashSet<ResourcePackageDetails>(installedPackageList.size());
for (InstalledPackage installedPackage : installedPackageList) {
ResourcePackageDetails transferPackage = ContentManagerHelper.installedPackageToDetails(installedPackage);
transferPackages.add(transferPackage);
}
DeletePackagesRequest transferRequest = new DeletePackagesRequest(persistedRequest.getId(), resourceId,
transferPackages);
// Make call to agent
try {
AgentClient agentClient = agentManager.getAgentClient(agent);
ContentAgentService agentService = agentClient.getContentAgentService();
agentService.deletePackages(transferRequest);
} catch (RuntimeException e) {
LOG.error("Error while sending deploy request to agent", e);
// Update the request with the failure
contentManager.failRequest(persistedRequest.getId(), e);
// Throw so caller knows an error happened
throw e;
}
}
@Override
@RequiredPermission(Permission.MANAGE_INVENTORY)
public void deletePackageVersion(Subject subject, int packageVersionId) {
Query q = entityManager.createNamedQuery(PackageVersion.DELETE_SINGLE_IF_NO_CONTENT_SOURCES_OR_REPOS);
q.setParameter("packageVersionId", packageVersionId);
q.executeUpdate();
}
@Override
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public ContentServiceRequest createRemoveRequest(int resourceId, String username, int[] installedPackageIds,
String requestNotes) {
Resource resource = entityManager.find(Resource.class, resourceId);
ContentServiceRequest persistedRequest = new ContentServiceRequest(resource, username,
ContentRequestType.DELETE);
persistedRequest.setStatus(ContentRequestStatus.IN_PROGRESS);
persistedRequest.setNotes(requestNotes);
long timestamp = System.currentTimeMillis();
for (int installedPackageId : installedPackageIds) {
// Load the InstalledPackage to get its package version for the relationship
InstalledPackage ip = entityManager.find(InstalledPackage.class, installedPackageId);
PackageVersion packageVersion = ip.getPackageVersion();
// Create the history entity
InstalledPackageHistory history = new InstalledPackageHistory();
history.setContentServiceRequest(persistedRequest);
history.setPackageVersion(packageVersion);
history.setResource(resource);
history.setStatus(InstalledPackageHistoryStatus.BEING_DELETED);
history.setTimestamp(timestamp);
persistedRequest.addInstalledPackageHistory(history);
}
entityManager.persist(persistedRequest);
return persistedRequest;
}
@Override
public void completeDeletePackageRequest(RemovePackagesResponse response) {
LOG.info("Completing delete package response: " + response);
// Load persisted request
Query query = entityManager.createNamedQuery(ContentServiceRequest.QUERY_FIND_BY_ID);
query.setParameter("id", response.getRequestId());
ContentServiceRequest persistedRequest = (ContentServiceRequest) query.getSingleResult();
Resource resource = persistedRequest.getResource();
int resourceTypeId = resource.getResourceType().getId();
// Update the persisted request
persistedRequest.setErrorMessage(response.getOverallRequestErrorMessage());
persistedRequest.setStatus(translateRequestResultStatus(response.getOverallRequestResult()));
// All history entries on the request at this point should be considered "in progress". We need to make
// sure each of these is closed out in some capacity. Typically, this will be done by the response
// explicitly indicating the result of each individual package. However, we can't rely on the plugin
// always doing this, so we need to keep track of which ones are not closed to prevent dangling in progress
// entries.
Set<InstalledPackageHistory> requestInProgressEntries = persistedRequest.getInstalledPackageHistory();
// Convert to a map so we can easily remove entries from it as they are closed by the individual
// package responses.
Map<PackageVersion, InstalledPackageHistory> inProgressEntries = new HashMap<PackageVersion, InstalledPackageHistory>(
requestInProgressEntries.size());
for (InstalledPackageHistory history : requestInProgressEntries) {
inProgressEntries.put(history.getPackageVersion(), history);
}
// Handle each individual package
long timestamp = System.currentTimeMillis();
for (RemoveIndividualPackageResponse singleResponse : response.getPackageResponses()) {
// Load the package version for the relationship
PackageDetailsKey key = singleResponse.getKey();
Query packageVersionQuery = entityManager
.createNamedQuery(PackageVersion.QUERY_FIND_BY_PACKAGE_DETAILS_KEY_WITH_NON_NULL_RESOURCE_TYPE);
packageVersionQuery.setParameter("packageName", key.getName());
packageVersionQuery.setParameter("packageTypeName", key.getPackageTypeName());
packageVersionQuery.setParameter("architectureName", key.getArchitectureName());
packageVersionQuery.setParameter("version", key.getVersion());
packageVersionQuery.setParameter("resourceTypeId", resourceTypeId);
PackageVersion packageVersion = (PackageVersion) packageVersionQuery.getSingleResult();
// Create the history entity
InstalledPackageHistory history = new InstalledPackageHistory();
history.setContentServiceRequest(persistedRequest);
history.setPackageVersion(packageVersion);
history.setResource(resource);
history.setTimestamp(timestamp);
if (singleResponse.getResult() == ContentResponseResult.SUCCESS) {
history.setStatus(InstalledPackageHistoryStatus.DELETED);
// We used to remove the InstalledPackage entity here, but now we'll rely on the plugin container
// to trigger a discovery after the delete request finishes.
} else {
history.setStatus(InstalledPackageHistoryStatus.FAILED);
history.setErrorMessage(singleResponse.getErrorMessage());
}
entityManager.persist(history);
persistedRequest.addInstalledPackageHistory(history);
// We're closing out the package request for this package version, so remove it from the cache of entries
// that need to be closed
inProgressEntries.remove(packageVersion);
}
// For any entries that were not closed, add closing entries
for (InstalledPackageHistory unclosed : inProgressEntries.values()) {
PackageVersion packageVersion = unclosed.getPackageVersion();
// Create the history entity
InstalledPackageHistory history = new InstalledPackageHistory();
history.setContentServiceRequest(persistedRequest);
history.setPackageVersion(packageVersion);
history.setResource(resource);
history.setTimestamp(timestamp);
// One option is to create a new status that indicates unknown. For now, just give them the same result
// as the overall request result
if (response.getOverallRequestResult() == ContentResponseResult.SUCCESS) {
history.setStatus(InstalledPackageHistoryStatus.DELETED);
} else {
history.setStatus(InstalledPackageHistoryStatus.FAILED);
}
entityManager.persist(history);
persistedRequest.addInstalledPackageHistory(history);
}
}
@Override
public void retrieveBitsFromResource(Subject user, int resourceId, int installedPackageId) {
LOG.info("Retrieving bits for package [" + installedPackageId + "] on resource ID [" + resourceId + "]");
// Check permissions first
if (!authorizationManager.hasResourcePermission(user, Permission.MANAGE_CONTENT, resourceId)) {
throw new PermissionException("User [" + user.getName() + "] does not have permission to delete package "
+ installedPackageId + " for resource ID [" + resourceId + "]");
}
// Load entities for references later in the method
Resource resource = entityManager.find(Resource.class, resourceId);
Agent agent = resource.getAgent();
InstalledPackage installedPackage = entityManager.find(InstalledPackage.class, installedPackageId);
// Persist in separate transaction so it is committed immediately, before the request is sent to the agent
ContentServiceRequest persistedRequest = contentManager.createRetrieveBitsRequest(resourceId, user.getName(),
installedPackageId);
// Package into transfer object
ResourcePackageDetails transferPackage = ContentManagerHelper.installedPackageToDetails(installedPackage);
RetrievePackageBitsRequest transferRequest = new RetrievePackageBitsRequest(persistedRequest.getId(),
resourceId, transferPackage);
// Make call to agent
try {
AgentClient agentClient = agentManager.getAgentClient(agent);
ContentAgentService agentService = agentClient.getContentAgentService();
agentService.retrievePackageBits(transferRequest);
} catch (RuntimeException e) {
LOG.error("Error while sending deploy request to agent", e);
// Update the request with the failure
contentManager.failRequest(persistedRequest.getId(), e);
// Throw so caller knows an error happened
throw e;
}
}
@Override
public byte[] getPackageBytes(Subject user, int resourceId, int installedPackageId) {
// Check permissions first
if (!authorizationManager.hasResourcePermission(user, Permission.MANAGE_CONTENT, resourceId)) {
throw new PermissionException("User [" + user.getName()
+ "] does not have permission to obtain package content for installed package id ["
+ installedPackageId + "] for resource ID [" + resourceId + "]");
}
try {
InstalledPackage installedPackage = entityManager.find(InstalledPackage.class, installedPackageId);
PackageBits bits = installedPackage.getPackageVersion().getPackageBits();
if (bits == null || bits.getBlob().getBits().length == 0) {
long start = System.currentTimeMillis();
retrieveBitsFromResource(user, resourceId, installedPackageId);
bits = installedPackage.getPackageVersion().getPackageBits();
while ((bits == null || bits.getBlob().getBits() == null)
&& (System.currentTimeMillis() - start < 30000)) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
entityManager.clear();
installedPackage = entityManager.find(InstalledPackage.class, installedPackageId);
bits = installedPackage.getPackageVersion().getPackageBits();
}
if (bits == null) {
throw new RuntimeException("Unable to retrieve package bits for resource: " + resourceId
+ " and package: " + installedPackageId + " before timeout.");
}
}
return bits.getBlob().getBits();
} catch (Exception e) {
throw new RuntimeException("Unable to retrieve package bits for resource: " + resourceId + " and package: "
+ installedPackageId + ".", e);
}
}
@Override
public List<DeployPackageStep> translateInstallationSteps(int resourceId, ResourcePackageDetails packageDetails)
throws Exception {
LOG.info("Retrieving installation steps for package [" + packageDetails + "]");
Resource resource = entityManager.find(Resource.class, resourceId);
Agent agent = resource.getAgent();
// Make call to agent
List<DeployPackageStep> packageStepList;
try {
AgentClient agentClient = agentManager.getAgentClient(agent);
ContentAgentService agentService = agentClient.getContentAgentService();
packageStepList = agentService.translateInstallationSteps(resourceId, packageDetails);
} catch (PluginContainerException e) {
LOG.error("Error while sending deploy request to agent", e);
// Throw so caller knows an error happened
throw e;
}
return packageStepList;
}
@Override
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public ContentServiceRequest createRetrieveBitsRequest(int resourceId, String username, int installedPackageId) {
Resource resource = entityManager.find(Resource.class, resourceId);
ContentServiceRequest persistedRequest = new ContentServiceRequest(resource, username,
ContentRequestType.GET_BITS);
persistedRequest.setStatus(ContentRequestStatus.IN_PROGRESS);
long timestamp = System.currentTimeMillis();
// Load the InstalledPackage to get its package version for the relationship
InstalledPackage ip = entityManager.find(InstalledPackage.class, installedPackageId);
PackageVersion packageVersion = ip.getPackageVersion();
// Create the history entity
InstalledPackageHistory history = new InstalledPackageHistory();
history.setContentServiceRequest(persistedRequest);
history.setPackageVersion(packageVersion);
history.setResource(resource);
history.setStatus(InstalledPackageHistoryStatus.BEING_RETRIEVED);
history.setTimestamp(timestamp);
persistedRequest.addInstalledPackageHistory(history);
entityManager.persist(persistedRequest);
return persistedRequest;
}
@Override
@TransactionTimeout(45 * 60)
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void completeRetrievePackageBitsRequest(ContentServiceResponse response, InputStream bitStream) {
LOG.info("Completing retrieve package bits response: " + response);
// Load persisted request
ContentServiceRequest persistedRequest = entityManager.find(ContentServiceRequest.class,
response.getRequestId());
// There is some inconsistency if we're completing a request that was not in the database
if (persistedRequest == null) {
LOG.error("Attempting to complete a request that was not found in the database: " + response.getRequestId());
return;
}
Resource resource = persistedRequest.getResource();
InstalledPackageHistory initialRequestHistory = persistedRequest.getInstalledPackageHistory().iterator().next();
PackageVersion packageVersion = initialRequestHistory.getPackageVersion();
if (response.getStatus() == ContentRequestStatus.SUCCESS) {
// Read the stream from the agent and store in the package version
try {
if (LOG.isDebugEnabled()) {
LOG.debug("Saving content for response: " + response);
}
PackageBits packageBits = initializePackageBits(null);
// Could use the following, but only on jdk6 as builds
// @since 1.6
// void setBinaryStream(int parameterIndex, java.io.InputStream x) throws SQLException;
Long length = packageVersion.getFileSize();
if (length == null) {
File tmpFile = File.createTempFile("rhq", ".stream");
FileOutputStream fos = new FileOutputStream(tmpFile);
length = StreamUtil.copy(bitStream, fos, true);
bitStream = new FileInputStream(tmpFile);
}
Connection conn = null;
PreparedStatement ps = null;
try {
PackageBits bits = entityManager.find(PackageBits.class, packageBits.getId());
String pkgName = "(set packageName)";
if ((packageVersion != null) && (packageVersion.getGeneralPackage() != null)) {
//update it to whatever package name is if we can get to it.
pkgName = packageVersion.getGeneralPackage().getName();
}
bits = loadPackageBits(bitStream, packageVersion.getId(), pkgName, packageVersion.getVersion(),
bits, null);
entityManager.merge(bits);
} finally {
if (ps != null) {
try {
ps.close();
} catch (Exception e) {
LOG.warn("Failed to close prepared statement for package version [" + packageVersion + "]");
}
}
if (conn != null) {
try {
conn.close();
} catch (Exception e) {
LOG.warn("Failed to close connection for package version [" + packageVersion + "]");
}
}
}
} catch (Exception e) {
LOG.error("Error while reading content from agent stream", e);
// TODO: don't want to throw exception here? does the tx rollback automatically anyway?
}
}
// Update the persisted request
persistedRequest.setErrorMessage(response.getErrorMessage());
persistedRequest.setStatus(response.getStatus());
// Add a new audit trail entry
InstalledPackageHistory completedHistory = new InstalledPackageHistory();
completedHistory.setContentServiceRequest(persistedRequest);
completedHistory.setResource(resource);
completedHistory.setTimestamp(System.currentTimeMillis());
completedHistory.setPackageVersion(packageVersion);
if (response.getStatus() == ContentRequestStatus.SUCCESS) {
completedHistory.setStatus(InstalledPackageHistoryStatus.RETRIEVED);
} else {
completedHistory.setStatus(InstalledPackageHistoryStatus.FAILED);
completedHistory.setErrorMessage(response.getErrorMessage());
}
}
@Override
@SuppressWarnings("unchecked")
public Set<ResourcePackageDetails> loadDependencies(int requestId, Set<PackageDetailsKey> keys) {
Set<ResourcePackageDetails> dependencies = new HashSet<ResourcePackageDetails>();
// Load the persisted request
ContentServiceRequest persistedRequest = entityManager.find(ContentServiceRequest.class, requestId);
// There is some inconsistency if the request is not in the database
if (persistedRequest == null) {
LOG.error("Could not find request with ID: " + requestId);
return dependencies;
}
// Load the resource so we can get its type for the package version queries
Resource resource = persistedRequest.getResource();
ResourceType resourceType = resource.getResourceType();
// For each package requested, load the package version and convert to a transfer object
long installationDate = System.currentTimeMillis();
for (PackageDetailsKey key : keys) {
Query packageQuery = entityManager
.createNamedQuery(PackageVersion.QUERY_FIND_BY_PACKAGE_DETAILS_KEY_WITH_NON_NULL_RESOURCE_TYPE);
packageQuery.setParameter("packageName", key.getName());
packageQuery.setParameter("packageTypeName", key.getPackageTypeName());
packageQuery.setParameter("architectureName", key.getArchitectureName());
packageQuery.setParameter("version", key.getVersion());
packageQuery.setParameter("resourceTypeId", resourceType.getId());
List persistedPackageList = packageQuery.getResultList();
// If we don't know anything about the package, skip it
if (persistedPackageList.size() == 0) {
continue;
}
if (persistedPackageList.size() != 1) {
LOG.error("Multiple packages found. Found: " + persistedPackageList.size() + " for key: " + key);
}
// Convert to transfer object to be sent to the agent
PackageVersion packageVersion = (PackageVersion) persistedPackageList.get(0);
ResourcePackageDetails details = ContentManagerHelper.packageVersionToDetails(packageVersion);
dependencies.add(details);
// Create an installed package history and attach to the request
InstalledPackageHistory dependencyPackage = new InstalledPackageHistory();
dependencyPackage.setContentServiceRequest(persistedRequest);
dependencyPackage.setPackageVersion(packageVersion);
dependencyPackage.setResource(resource);
dependencyPackage.setStatus(InstalledPackageHistoryStatus.BEING_INSTALLED);
dependencyPackage.setTimestamp(installationDate);
persistedRequest.addInstalledPackageHistory(dependencyPackage);
entityManager.persist(dependencyPackage);
}
return dependencies;
}
@Override
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void failRequest(int requestId, Throwable error) {
Query query = entityManager.createNamedQuery(ContentServiceRequest.QUERY_FIND_BY_ID);
query.setParameter("id", requestId);
ContentServiceRequest persistedRequest = (ContentServiceRequest) query.getSingleResult();
Resource resource = persistedRequest.getResource();
persistedRequest.setErrorMessage(ThrowableUtil.getStackAsString(error));
persistedRequest.setStatus(ContentRequestStatus.FAILURE);
// This should only be called as the result of an exception during the user initiated action. As such,
// every package history entity represents an in progress state. Add a new entry for each in the failed state.
long timestamp = System.currentTimeMillis();
for (InstalledPackageHistory history : persistedRequest.getInstalledPackageHistory()) {
InstalledPackageHistory failedEntry = new InstalledPackageHistory();
failedEntry.setContentServiceRequest(persistedRequest);
failedEntry.setDeploymentConfigurationValues(history.getDeploymentConfigurationValues());
failedEntry.setErrorMessage(ThrowableUtil.getStackAsString(error));
failedEntry.setPackageVersion(history.getPackageVersion());
failedEntry.setResource(resource);
failedEntry.setStatus(InstalledPackageHistoryStatus.FAILED);
failedEntry.setTimestamp(timestamp);
persistedRequest.addInstalledPackageHistory(failedEntry);
}
}
@Override
@SuppressWarnings("unchecked")
public List<Architecture> findArchitectures(Subject subject) {
Query q = entityManager.createNamedQuery(Architecture.QUERY_FIND_ALL);
List<Architecture> architectures = q.getResultList();
return architectures;
}
@Override
public Architecture getNoArchitecture() {
Query q = entityManager.createNamedQuery(Architecture.QUERY_FIND_BY_NAME);
q.setParameter("name", "noarch");
Architecture architecture = (Architecture) q.getSingleResult();
return architecture;
}
@Override
@SuppressWarnings("unchecked")
public List<PackageType> findPackageTypes(Subject subject, String resourceTypeName, String pluginName)
throws ResourceTypeNotFoundException {
ResourceType rt = resourceTypeManager.getResourceTypeByNameAndPlugin(subject, resourceTypeName, pluginName);
if (null == rt) {
throw new ResourceTypeNotFoundException(resourceTypeName);
}
Query query = entityManager.createNamedQuery(PackageType.QUERY_FIND_BY_RESOURCE_TYPE_ID);
query.setParameter("typeId", rt.getId());
List<PackageType> result = query.getResultList();
return result;
}
@Override
public PackageType findPackageType(Subject subject, Integer resourceTypeId, String packageTypeName) {
Query q = entityManager
.createNamedQuery(resourceTypeId == null ? PackageType.QUERY_FIND_BY_NAME_AND_NULL_RESOURCE_TYPE
: PackageType.QUERY_FIND_BY_RESOURCE_TYPE_ID_AND_NAME);
if (resourceTypeId != null) {
q.setParameter("typeId", resourceTypeId);
}
q.setParameter("name", packageTypeName);
@SuppressWarnings("unchecked")
List<PackageType> results = q.getResultList();
if (results.size() == 0) {
return null;
} else if (results.size() == 1) {
return results.get(0);
} else {
String message = "2 or more package types with name '" + packageTypeName
+ "' found on the resource type with id " + resourceTypeId + ". This is a bug in the database.";
LOG.error(message);
throw new IllegalStateException(message);
}
}
@Override
public PackageTypeAndVersionFormatComposite findPackageTypeWithVersionFormat(Subject subject,
Integer resourceTypeId, String packageTypeName) {
PackageType type = findPackageType(subject, resourceTypeId, packageTypeName);
PackageVersionFormatDescription format = null;
try {
PackageTypeBehavior behavior = ContentManagerHelper.getPackageTypeBehavior(packageTypeName);
if (behavior != null) {
format = behavior.getPackageVersionFormat(packageTypeName);
}
} catch (Exception e) {
//well, this shouldn't happen but is not crucial in this case
LOG.info("Failed to obtain the behavior of package type '" + packageTypeName + "'.", e);
}
return new PackageTypeAndVersionFormatComposite(type, format);
}
@Override
@SuppressWarnings("unchecked")
public void checkForTimedOutRequests(Subject subject) {
if (!authorizationManager.isOverlord(subject)) {
if (LOG.isDebugEnabled()) {
LOG.debug("Unauthorized user " + subject + " tried to execute checkForTimedOutRequests; "
+ "only the overlord may execute this system operation");
}
return;
}
try {
Query query = entityManager.createNamedQuery(ContentServiceRequest.QUERY_FIND_WITH_STATUS);
query.setParameter("status", ContentRequestStatus.IN_PROGRESS);
List<ContentServiceRequest> inProgressRequests = query.getResultList();
if (inProgressRequests == null) {
return;
}
long timestamp = System.currentTimeMillis();
for (ContentServiceRequest request : inProgressRequests) {
long duration = request.getDuration();
// If the duration exceeds the timeout threshold, mark it as timed out
if (duration > REQUEST_TIMEOUT) {
if (LOG.isDebugEnabled()) {
LOG.debug("Timing out request after duration: " + duration + " Request: " + request);
}
request.setErrorMessage("Request with duration " + duration + " exceeded the timeout threshold of "
+ REQUEST_TIMEOUT);
request.setStatus(ContentRequestStatus.TIMED_OUT);
Resource resource = request.getResource();
// Need to add audit trail entries for each package as well, so the audit trail doesn't read
// as the operation is still being performed
Set<InstalledPackageHistory> requestPackages = request.getInstalledPackageHistory();
for (InstalledPackageHistory history : requestPackages) {
InstalledPackageHistoryStatus packageStatus = history.getStatus();
// Just to be safe, we're only going to "close out" any in progress entries. All entries in this
// list will likely be in this state, and we'd need to handle resubmissions differently anyway.
switch (packageStatus) {
case BEING_DELETED:
case BEING_INSTALLED:
case BEING_RETRIEVED:
InstalledPackageHistory closedHistory = new InstalledPackageHistory();
closedHistory.setContentServiceRequest(request);
closedHistory.setPackageVersion(history.getPackageVersion());
closedHistory.setResource(resource);
closedHistory.setStatus(InstalledPackageHistoryStatus.TIMED_OUT);
closedHistory.setTimestamp(timestamp);
entityManager.persist(closedHistory);
break;
default:
LOG.warn("Found a history entry on the request with an unexpected status. Id: "
+ history.getId() + ", Status: " + packageStatus);
break;
}
}
}
}
} catch (Throwable e) {
LOG.error("Error while processing timed out requests", e);
}
}
@Override
public PackageVersion createPackageVersion(Subject subject, String packageName, int packageTypeId, String version,
Integer architectureId, byte[] packageBytes) {
return createPackageVersionWithDisplayVersion(subject, packageName, packageTypeId, version, null,
architectureId, packageBytes);
}
@Override
public PackageVersion createPackageVersionWithDisplayVersion(Subject subject, String packageName,
int packageTypeId, String version, String displayVersion, Integer architectureId, byte[] packageBytes) {
// Check permissions first
if (!authorizationManager.hasGlobalPermission(subject, Permission.MANAGE_CONTENT)) {
throw new PermissionException("User [" + subject.getName()
+ "] does not have permission to create package versions");
}
return createPackageVersionWithDisplayVersion(subject, packageName, packageTypeId, version, displayVersion,
(null == architectureId) ? getNoArchitecture().getId() : architectureId, new ByteArrayInputStream(
packageBytes));
}
@Override
@TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW)
public PackageVersion createPackageVersionWithDisplayVersion(Subject subject, String packageName,
int packageTypeId, String version, String displayVersion, int architectureId, InputStream packageBitStream) {
// See if the package version already exists and return that if it does
Query packageVersionQuery = entityManager.createNamedQuery(PackageVersion.QUERY_FIND_BY_PACKAGE_VER_ARCH);
packageVersionQuery.setParameter("name", packageName);
packageVersionQuery.setParameter("packageTypeId", packageTypeId);
packageVersionQuery.setParameter("architectureId", architectureId);
packageVersionQuery.setParameter("version", version);
// Result of the query should be either 0 or 1
List existingVersionList = packageVersionQuery.getResultList();
if (existingVersionList.size() > 0) {
PackageVersion existingPackageVersion = (PackageVersion) existingVersionList.get(0);
if (displayVersion != null && !displayVersion.trim().isEmpty()) {
existingPackageVersion.setDisplayVersion(displayVersion);
existingPackageVersion = persistOrMergePackageVersionSafely(existingPackageVersion);
}
return existingPackageVersion;
}
Architecture architecture = entityManager.find(Architecture.class, architectureId);
PackageType packageType = entityManager.find(PackageType.class, packageTypeId);
//check the validity of the provided data
try {
PackageTypeBehavior behavior = ContentManagerHelper.getPackageTypeBehavior(packageTypeId);
ValidatablePackageDetailsKey key = new ValidatablePackageDetailsKey(packageName, version,
packageType.getName(), architecture.getName());
behavior.validateDetails(key, subject);
packageName = key.getName();
version = key.getVersion();
if (!architecture.getName().equals(key.getArchitectureName())) {
Query q = entityManager.createNamedQuery(Architecture.QUERY_FIND_BY_NAME);
q.setParameter("name", key.getArchitectureName());
architecture = (Architecture) q.getSingleResult();
}
} catch (PackageDetailsValidationException e) {
throw e;
} catch (Exception e) {
LOG.error("Failed to get the package type plugin container. This is a bug.", e);
throw new IllegalStateException("Failed to get the package type plugin container.", e);
}
// If the package doesn't exist, create that here
Query packageQuery = entityManager.createNamedQuery(Package.QUERY_FIND_BY_NAME_PKG_TYPE_ID);
packageQuery.setParameter("name", packageName);
packageQuery.setParameter("packageTypeId", packageTypeId);
Package existingPackage;
List existingPackageList = packageQuery.getResultList();
if (existingPackageList.size() == 0) {
existingPackage = new Package(packageName, packageType);
existingPackage = persistOrMergePackageSafely(existingPackage);
} else {
existingPackage = (Package) existingPackageList.get(0);
}
// Create a package version and add it to the package
PackageVersion newPackageVersion = new PackageVersion(existingPackage, version, architecture);
newPackageVersion.setDisplayName(existingPackage.getName());
newPackageVersion = persistOrMergePackageVersionSafely(newPackageVersion);
Map<String, String> contentDetails = new HashMap<String, String>();
PackageBits bits = loadPackageBits(packageBitStream, newPackageVersion.getId(), packageName, version, null,
contentDetails);
newPackageVersion.setPackageBits(bits);
newPackageVersion.setFileSize(Long.valueOf(contentDetails.get(UPLOAD_FILE_SIZE)).longValue());
newPackageVersion.setSHA256(contentDetails.get(UPLOAD_SHA256));
newPackageVersion.setDisplayVersion(displayVersion);
existingPackage.addVersion(newPackageVersion);
return newPackageVersion;
}
@Override
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public PackageVersion persistPackageVersion(PackageVersion pv) {
// EM.persist requires related entities to be attached, let's attach them now
// package has persist cascade enabled, so skip loading it if we'll allow it to be created here
if (pv.getGeneralPackage().getId() > 0) {
pv.setGeneralPackage(entityManager.find(Package.class, pv.getGeneralPackage().getId()));
}
// arch has persist cascade enabled, so skip loading it if we'll allow it to be created here
if (pv.getArchitecture().getId() > 0) {
pv.setArchitecture(entityManager.find(Architecture.class, pv.getArchitecture().getId()));
}
// config is optional but has persist cascade enabled, so skip loading it if we'll allow it to be created here
if (pv.getExtraProperties() != null && pv.getExtraProperties().getId() > 0) {
pv.setExtraProperties(entityManager.find(Configuration.class, pv.getExtraProperties().getId()));
}
// our object's relations are now full attached, we can persist it
entityManager.persist(pv);
return pv;
}
@Override
@SuppressWarnings("unchecked")
public PackageVersion persistOrMergePackageVersionSafely(PackageVersion pv) {
PackageVersion persisted = null;
RuntimeException error = null;
try {
if (pv.getId() == 0) {
persisted = contentManager.persistPackageVersion(pv);
}
} catch (RuntimeException re) {
error = re;
}
// If we didn't persist, the PV already exists, so we should be able to find it.
if (persisted == null) {
Query q = entityManager.createNamedQuery(PackageVersion.QUERY_FIND_BY_PACKAGE_DETAILS_KEY);
q.setParameter("packageName", pv.getGeneralPackage().getName());
q.setParameter("packageTypeName", pv.getGeneralPackage().getPackageType().getName());
q.setParameter("architectureName", pv.getArchitecture().getName());
q.setParameter("version", pv.getVersion());
ResourceType rt = pv.getGeneralPackage().getPackageType().getResourceType();
q.setParameter("resourceType", rt);
List<PackageVersion> found = q.getResultList();
if (error != null && found.size() == 0) {
throw error;
}
if (found.size() != 1) {
throw new RuntimeException("Expecting 1 package version matching [" + pv + "] but got: " + found);
}
pv.setId(found.get(0).getId());
persisted = entityManager.merge(pv);
if (error != null) {
LOG.warn("There was probably a very big and ugly EJB/hibernate error just above this log message - "
+ "you can normally ignore that. We detected that a package version was already created when we"
+ " tried to do it also - we will ignore this and just use the new package version that was "
+ "created in the other thread", new Throwable("Stack Trace:"));
}
} else {
// the persisted object is unattached right now,
// we want it attached so the caller always has an attached entity returned to it
persisted = entityManager.find(PackageVersion.class, persisted.getId());
persisted.getGeneralPackage().getId();
persisted.getArchitecture().getId();
if (persisted.getExtraProperties() != null) {
persisted.getExtraProperties().getId();
}
}
return persisted;
}
@Override
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public Package persistPackage(Package pkg) {
// EM.persist requires related entities to be attached, let's attach them now
pkg.setPackageType(entityManager.find(PackageType.class, pkg.getPackageType().getId()));
// our object's relations are now full attached, we can persist it
entityManager.persist(pkg);
return pkg;
}
@Override
@SuppressWarnings("unchecked")
public Package persistOrMergePackageSafely(Package pkg) {
Package persisted = null;
RuntimeException error = null;
try {
if (pkg.getId() == 0) {
persisted = contentManager.persistPackage(pkg);
}
} catch (RuntimeException re) {
error = re;
}
// If we didn't persist, the package already exists, so we should be able to find it.
if (persisted == null) {
Query q = entityManager.createNamedQuery(Package.QUERY_FIND_BY_NAME_PKG_TYPE_ID);
q.setParameter("name", pkg.getName());
q.setParameter("packageTypeId", pkg.getPackageType().getId());
List<Package> found = q.getResultList();
if (error != null && found.size() == 0) {
throw error;
}
if (found.size() != 1) {
throw new RuntimeException("Expecting 1 package matching [" + pkg + "] but got: " + found);
}
pkg.setId(found.get(0).getId());
persisted = entityManager.merge(pkg);
if (error != null) {
LOG.warn("There was probably a very big and ugly EJB/hibernate error just above this log message - "
+ "you can normally ignore that. We detected that a package was already created when we"
+ " tried to do it also - we will ignore this and just use the new package that was "
+ "created in the other thread");
}
} else {
// the persisted object is unattached right now,
// we want it attached so the caller always has an attached entity returned to it
persisted = entityManager.find(Package.class, persisted.getId());
persisted.getPackageType().getId();
}
return persisted;
}
@Override
public PackageType getResourceCreationPackageType(int resourceTypeId) {
Query query = entityManager.createNamedQuery(PackageType.QUERY_FIND_BY_RESOURCE_TYPE_ID_AND_CREATION_FLAG);
query.setParameter("typeId", resourceTypeId);
PackageType packageType = (PackageType) query.getSingleResult();
return packageType;
}
// Private --------------------------------------------
private ContentRequestStatus translateRequestResultStatus(ContentResponseResult result) {
switch (result) {
case SUCCESS: {
return ContentRequestStatus.SUCCESS;
}
default: {
return ContentRequestStatus.FAILURE;
}
}
}
/**
* Translates the transfer object representation of package deployment steps into domain entities.
*
* @param transferSteps cannot be <code>null</code>
* @param history history item the steps are a part of, this will be used when creating the domain entities
* to establish the relationship
* @return list of domain entities
*/
private List<PackageInstallationStep> translateInstallationSteps(List<DeployPackageStep> transferSteps,
InstalledPackageHistory history) {
List<PackageInstallationStep> steps = new ArrayList<PackageInstallationStep>(transferSteps.size());
int stepOrder = 0;
for (DeployPackageStep transferStep : transferSteps) {
PackageInstallationStep step = new PackageInstallationStep();
step.setDescription(transferStep.getDescription());
step.setKey(transferStep.getStepKey());
step.setResult(transferStep.getStepResult());
step.setErrorMessage(transferStep.getStepErrorMessage());
step.setOrder(stepOrder++);
step.setInstalledPackageHistory(history);
steps.add(step);
}
return steps;
}
@Override
@SuppressWarnings("unchecked")
public List<String> findInstalledPackageVersions(Subject user, int resourceId) {
Query query = entityManager.createNamedQuery(InstalledPackage.QUERY_FIND_PACKAGE_LIST_VERSIONS);
query.setParameter("resourceId", resourceId);
List<String> packages = query.getResultList();
return packages;
}
@Override
@SuppressWarnings("unchecked")
public PageList<InstalledPackage> findInstalledPackagesByCriteria(Subject subject, InstalledPackageCriteria criteria) {
CriteriaQueryGenerator generator = new CriteriaQueryGenerator(subject, criteria);
;
if (!authorizationManager.isInventoryManager(subject)) {
// Ensure we limit to packages installed to viewable resources
generator.setAuthorizationResourceFragment(CriteriaQueryGenerator.AuthorizationTokenType.RESOURCE,
"resource", subject.getId());
}
CriteriaQueryRunner<InstalledPackage> queryRunner = new CriteriaQueryRunner(criteria, generator, entityManager);
return queryRunner.execute();
}
@Override
@SuppressWarnings("unchecked")
public PageList<PackageVersion> findPackageVersionsByCriteria(Subject subject, PackageVersionCriteria criteria) {
Integer resourceId = criteria.getFilterResourceId();
if (!authorizationManager.isInventoryManager(subject)) {
if ((null == resourceId) || criteria.isInventoryManagerRequired()) {
throw new PermissionException("Subject [" + subject.getName()
+ "] requires InventoryManager permission for requested query criteria.");
} else if (!authorizationManager.canViewResource(subject, resourceId)) {
throw new PermissionException("Subject [" + subject.getName()
+ "] does not have permission to view the specified resource.");
}
}
CriteriaQueryGenerator generator = new CriteriaQueryGenerator(subject, criteria);
;
CriteriaQueryRunner<PackageVersion> queryRunner = new CriteriaQueryRunner(criteria, generator, entityManager);
return queryRunner.execute();
}
@Override
public PageList<Package> findPackagesByCriteria(Subject subject, PackageCriteria criteria) {
if (criteria.getFilterRepoId() != null) {
if (!authorizationManager.canViewRepo(subject, criteria.getFilterRepoId())) {
throw new PermissionException("Subject [" + subject.getName() + "] cannot view the repo with id "
+ criteria.getFilterRepoId());
}
} else if (!authorizationManager.hasGlobalPermission(subject, Permission.MANAGE_REPOSITORIES)) {
throw new PermissionException("Only repository managers can search for packages across all repos.");
}
CriteriaQueryGenerator generator = new CriteriaQueryGenerator(subject, criteria);
CriteriaQueryRunner<Package> runner = new CriteriaQueryRunner<Package>(criteria, generator, entityManager);
return runner.execute();
}
@Override
public PageList<PackageAndLatestVersionComposite> findPackagesWithLatestVersion(Subject subject,
PackageCriteria criteria) {
if (criteria.getFilterRepoId() == null) {
throw new IllegalArgumentException("The criteria query has to have a filter for a specific repo.");
}
criteria.fetchVersions(true);
PageList<Package> packages = findPackagesByCriteria(subject, criteria);
PageList<PackageAndLatestVersionComposite> ret = new PageList<PackageAndLatestVersionComposite>(
packages.getTotalSize(), packages.getPageControl());
for (Package p : packages) {
PackageVersion latest = repoManager.getLatestPackageVersion(subject, p.getId(), criteria.getFilterRepoId());
ret.add(new PackageAndLatestVersionComposite(p, latest));
}
return ret;
}
@Override
public InstalledPackage getBackingPackageForResource(Subject subject, int resourceId) {
InstalledPackage result = null;
// check if the resource is content backed if not, return null
Resource res = resourceManager.getResourceById(subject, resourceId);
ResourceType type = res.getResourceType();
if (!ResourceCreationDataType.CONTENT.equals(type.getCreationDataType())) {
return null;
}
InstalledPackageCriteria criteria = new InstalledPackageCriteria();
criteria.addFilterResourceId(resourceId);
PageList<InstalledPackage> ips = findInstalledPackagesByCriteria(subject, criteria);
// should not be more than 1
if ((null != ips) && (ips.size() > 0)) {
int mostRecentPackageIndex = 0;
if (ips.size() > 1) {
for (int index = 1; index < ips.size(); index++) {
if (ips.get(index).getInstallationDate() > ips.get(mostRecentPackageIndex).getInstallationDate()) {
mostRecentPackageIndex = index;
}
}
}
result = ips.get(mostRecentPackageIndex);
// fetch these
result.getPackageVersion().getGeneralPackage().getId();
result.getPackageVersion().getGeneralPackage().getPackageType().getId();
result.getPackageVersion().getArchitecture().getId();
}
return result;
}
/** Does much of same functionality as createPackageVersion, but uses same named query
* as the agent side discovery mechanism, and passes in additional parameters available
* when file has been uploaded via the UI.
*/
@Override
@SuppressWarnings("unchecked")
public PackageVersion getUploadedPackageVersion(Subject subject, String packageName, int packageTypeId,
String version, int architectureId, InputStream packageBitStream, Map<String, String> packageUploadDetails,
Integer repoId) {
PackageVersion packageVersion = null;
//default version to 1.0 if is null, not provided for any reason.
if ((version == null) || (version.trim().length() == 0)) {
version = "1.0";
}
Architecture architecture = entityManager.find(Architecture.class, architectureId);
PackageType packageType = entityManager.find(PackageType.class, packageTypeId);
// See if package version already exists for the resource package
Query packageVersionQuery = null;
if (packageType.getResourceType() != null) {
packageVersionQuery = entityManager
.createNamedQuery(PackageVersion.QUERY_FIND_BY_PACKAGE_DETAILS_KEY_WITH_NON_NULL_RESOURCE_TYPE);
packageVersionQuery.setParameter("resourceTypeId", packageType.getResourceType().getId());
} else {
packageVersionQuery = entityManager.createNamedQuery(PackageVersion.QUERY_FIND_BY_PACKAGE_DETAILS_KEY);
packageVersionQuery.setParameter("resourceType", null);
}
packageVersionQuery.setFlushMode(FlushModeType.COMMIT);
packageVersionQuery.setParameter("packageName", packageName);
packageVersionQuery.setParameter("packageTypeName", packageType.getName());
packageVersionQuery.setParameter("architectureName", architecture.getName());
packageVersionQuery.setParameter("version", version);
// Result of the query should be either 0 or 1
List<PackageVersion> existingPackageVersionList = packageVersionQuery.getResultList();
if (existingPackageVersionList.size() > 0) {
packageVersion = existingPackageVersionList.get(0);
}
try {
PackageTypeBehavior behavior = ContentManagerHelper.getPackageTypeBehavior(packageTypeId);
if (behavior != null) {
String packageTypeName = packageType.getName();
String archName = architecture.getName();
ValidatablePackageDetailsKey key = new ValidatablePackageDetailsKey(packageName, version,
packageTypeName, archName);
behavior.validateDetails(key, subject);
//update the details from the validation results
packageName = key.getName();
version = key.getVersion();
if (!architecture.getName().equals(key.getArchitectureName())) {
Query q = entityManager.createNamedQuery(Architecture.QUERY_FIND_BY_NAME);
q.setParameter("name", key.getArchitectureName());
architecture = (Architecture) q.getSingleResult();
}
}
} catch (PackageDetailsValidationException e) {
throw e;
} catch (Exception e) {
LOG.error("Failed to get the package type plugin container. This is a bug.", e);
throw new IllegalStateException("Failed to get the package type plugin container.", e);
}
Package existingPackage = null;
Query packageQuery = entityManager.createNamedQuery(Package.QUERY_FIND_BY_NAME_PKG_TYPE_ID);
packageQuery.setParameter("name", packageName);
packageQuery.setParameter("packageTypeId", packageTypeId);
List<Package> existingPackageList = packageQuery.getResultList();
if (existingPackageList.size() == 0) {
// If the package doesn't exist, create that here
existingPackage = new Package(packageName, packageType);
existingPackage = persistOrMergePackageSafely(existingPackage);
} else {
existingPackage = existingPackageList.get(0);
}
//initialize package version if not already
if (packageVersion == null) {
packageVersion = new PackageVersion(existingPackage, version, architecture);
packageVersion.setDisplayName(existingPackage.getName());
entityManager.persist(packageVersion);
}
// We are going to replace the package bits a bit later
// before it happen lets purge the BLOB to avoid leaks
if (packageVersion.getPackageBits() != null &&
DatabaseTypeFactory.isPostgres(DatabaseTypeFactory.getDefaultDatabaseType())) {
contentManager.unlinkBlob(packageVersion.getPackageBits().getId());
}
//get the data
Map<String, String> contentDetails = new HashMap<String, String>();
PackageBits bits = loadPackageBits(packageBitStream, packageVersion.getId(), packageName, version, null,
contentDetails);
packageVersion.setPackageBits(bits);
packageVersion.setFileSize(Long.valueOf(contentDetails.get(UPLOAD_FILE_SIZE)).longValue());
packageVersion.setSHA256(contentDetails.get(UPLOAD_SHA256));
//populate extra details, persist
if (packageUploadDetails != null) {
packageVersion.setFileCreatedDate(Long.valueOf(packageUploadDetails
.get(ContentManagerLocal.UPLOAD_FILE_INSTALL_DATE)));
packageVersion.setFileName(packageUploadDetails.get(ContentManagerLocal.UPLOAD_FILE_NAME));
packageVersion.setMD5(packageUploadDetails.get(ContentManagerLocal.UPLOAD_MD5));
packageVersion.setDisplayVersion(packageUploadDetails.get(ContentManagerLocal.UPLOAD_DISPLAY_VERSION));
}
entityManager.merge(packageVersion);
if (repoId != null) {
int[] packageVersionIds = new int[] { packageVersion.getId() };
repoManager.addPackageVersionsToRepo(subject, repoId, packageVersionIds);
}
entityManager.flush();
return packageVersion;
}
@Override
public PackageType persistServersidePackageType(PackageType packageType) {
if (packageType.getResourceType() != null) {
throw new IllegalArgumentException("Server-side package types can't be associated with a resource type.");
}
entityManager.persist(packageType);
return packageType;
}
/** Pulls in package bits from the stream. Currently inefficient.
*
* @param packageBitStream
* @param packageVersionId
* @param contentDetails
* @return PackageBits ref populated.
*/
private PackageBits loadPackageBits(InputStream packageBitStream, int packageVersionId, String packageName,
String packageVersion, PackageBits existingBits, Map<String, String> contentDetails) {
// If/When H2 handles blob update/streaming blobs we can get rid of this conditional code
if (DatabaseTypeFactory.isH2(DatabaseTypeFactory.getDefaultDatabaseType())) {
return loadPackageBitsH2(packageBitStream, packageVersionId, packageName, packageVersion, existingBits,
contentDetails);
}
// use existing or instantiate PackageBits instance.
PackageBits bits = (null == existingBits) ? initializePackageBits(null) : existingBits;
//locate related packageVersion
PackageVersion pv = entityManager.find(PackageVersion.class, packageVersionId);
//associate the two if located.
if (null != pv) {
pv.setPackageBits(bits);
entityManager.flush();
}
//write data from stream into db using Hibernate Blob mechanism
updateBlobStream(packageBitStream, bits, contentDetails);
return bits;
}
private PackageBits loadPackageBitsH2(InputStream packageBitStream, int packageVersionId, String packageName,
String packageVersion, PackageBits existingBits, Map<String, String> contentDetails) {
PackageBits bits = null;
PackageBitsBlob blob = null;
// The blob cannot be updated, so we'll need to create a whole new row.
if (null != existingBits) {
blob = entityManager.find(PackageBitsBlob.class, existingBits.getId());
entityManager.remove(blob);
entityManager.flush();
}
// We have to work backwards to avoid constraint violations. PackageBits requires a PackageBitsBlob,
// so create and persist that first, getting the ID
blob = new PackageBitsBlob();
// just set the blob now, no streaming. The assumption is that H2 (demo) will not be using large blobs
byte[] bytes = StreamUtil.slurp(packageBitStream);
blob.setBits(bytes);
entityManager.persist(blob);
entityManager.flush();
// Now create the PackageBits entity and assign the Id and blob. Note, do not persist the
// entity, the row already exists (due to the blob persist above). Just perform and flush the update.
bits = new PackageBits();
bits.setId(blob.getId());
bits.setBlob(blob);
entityManager.flush();
//locate related packageVersion
PackageVersion pv = entityManager.find(PackageVersion.class, packageVersionId);
//associate the two if packageVersion exists.
if (null != pv) {
pv.setPackageBits(bits);
entityManager.flush();
}
// update contentDetails in needed
if (null != contentDetails) {
contentDetails.put(UPLOAD_FILE_SIZE, String.valueOf(bytes.length));
try {
contentDetails.put(UPLOAD_SHA256,
new MessageDigestGenerator(MessageDigestGenerator.SHA_256).calcDigestString(bytes));
} catch (Exception e) {
throw new RuntimeException("Failed to calculate SHA256 for package bits: ", e);
}
}
return bits;
}
/**
* This creates a new PackageBits entity initialized to EMPTY_BLOB for the associated PackageBitsBlob.
* Note that PackageBits and PackageBitsBlob are two entities that *share* the same db row. This is
* done to allow for Lazy load semantics on the Lob. Hibernate does not honor field-level Lazy load
* on a Lob unless the entity class is instrumented. We can't usethat approach because it introduces
* hibernate imports into the domain class, and that violates our restriction of exposing hibernate
* classes to the Agent and Remote clients.
*
* @return
*/
private PackageBits initializePackageBits(PackageBits bits) {
if (null == bits) {
PackageBitsBlob blob = null;
// We have to work backwards to avoid constraint violations. PackageBits requires a PackageBitsBlob,
// so create and persist that first, getting the ID
blob = new PackageBitsBlob();
blob.setBits(PackageBits.EMPTY_BLOB.getBytes());
entityManager.persist(blob);
// Now create the PackageBits entity and assign the Id and blob. Note, do not persist the
// entity, the row already exists. Just perform and flush the update.
bits = new PackageBits();
bits.setId(blob.getId());
bits.setBlob(blob);
} else {
PackageBitsBlob blob = entityManager.find(PackageBitsBlob.class, bits.getId());
// don't bother testing for null, that may pull a large blob, just make sure it's not null
blob.setBits(PackageBits.EMPTY_BLOB.getBytes());
}
// write to the db and return the new PackageBits and associated PackageBitsBlob
entityManager.flush();
return bits;
}
/** Takes an input stream and copies it into the PackageBits table using Hibernate
* Blob mechanism with PreparedStatements. As all content into Bits are not stored as type OID, t
*
* @param stream
* @param contentDetails Map to store content details in used in PackageVersioning
*/
@Override
public void updateBlobStream(InputStream stream, PackageBits bits, Map<String, String> contentDetails) {
//TODO: are there any db specific limits that we should check/verify here before stuffing
// the contents of a stream into the db? Should we just let the db complain and take care of
// input validation?
if (stream == null) {
return; // no stream content to update.
}
bits = initializePackageBits(bits);
//locate the existing PackageBitsBlob instance
bits = entityManager.find(PackageBits.class, bits.getId());
PackageBitsBlob blob = bits.getBlob();
//Create prepared statements to work with Blobs and hibernate.
Connection conn = null;
PreparedStatement ps = null;
PreparedStatement ps2 = null;
try {
conn = dataSource.getConnection();
//we are loading the PackageBits saved in the previous step
//we need to lock the row which will be updated so we are using FOR UPDATE
ps = conn.prepareStatement("SELECT BITS FROM " + PackageBits.TABLE_NAME + " WHERE ID = ? FOR UPDATE");
ps.setInt(1, bits.getId());
ResultSet rs = ps.executeQuery();
try {
while (rs.next()) {
//We can not create a blob directly because BlobImpl from Hibernate is not acceptable
//for oracle and Connection.createBlob is not working on postgres.
//This blob will be not empty because we saved there PackageBits.EMPTY_BLOB
Blob blb = rs.getBlob(1);
//copy the stream to the Blob
copyAndDigest(stream, blb.setBinaryStream(1), false, contentDetails);
stream.close();
if(!DatabaseTypeFactory.isPostgres(DatabaseTypeFactory.getDefaultDatabaseType())) {
//populate the prepared statement for update
ps2 = conn.prepareStatement("UPDATE " + PackageBits.TABLE_NAME + " SET bits = ? where id = ?");
ps2.setBlob(1, blb);
ps2.setInt(2, bits.getId());
//initiate the update.
if (ps2.execute()) {
throw new Exception("Unable to upload the package bits to the DB:");
}
ps2.close();
}
}
} finally {
rs.close();
}
ps.close();
conn.close();
} catch (Exception e) {
LOG.error("An error occurred while updating Blob with stream for PackageBits[" + bits.getId() + "], "
+ e.getMessage());
if (e instanceof RuntimeException && e.getCause() != null && e.getCause().getMessage().startsWith("ORA-")) {
throw (RuntimeException) e;
} else {
e.printStackTrace();
}
} finally {
if (ps != null) {
try {
ps.close();
} catch (Exception e) {
LOG.warn("Failed to close prepared statement for package bits [" + bits.getId() + "]");
}
}
if (ps2 != null) {
try {
ps2.close();
} catch (Exception e) {
LOG.warn("Failed to close prepared statement for package bits [" + bits.getId() + "]");
}
}
if (conn != null) {
try {
conn.close();
} catch (Exception e) {
LOG.warn("Failed to close connection for package bits [" + bits.getId() + "]");
}
}
if (stream != null) {
try {
stream.close();
} catch (Exception e) {
LOG.warn("Failed to close stream to package bits located at [" + +bits.getId() + "]");
}
}
}
// not sure this merge (or others like it in this file are necessary...
entityManager.merge(bits);
entityManager.flush();
}
/** Functions same as StreamUtil.copy(), but calculates SHA hash and file size and write it to
* the Map<String,String> passed in.
*
* @param input
* @param output
* @param closeStreams
* @param contentDetails
* @return
* @throws RuntimeException
*/
@SuppressWarnings("resource")
private long copyAndDigest(InputStream input, OutputStream output, boolean closeStreams,
Map<String, String> contentDetails) throws RuntimeException {
long numBytesCopied = 0;
int bufferSize = 32768;
MessageDigestGenerator digestGenerator = null;
if (contentDetails != null) {
digestGenerator = new MessageDigestGenerator(MessageDigestGenerator.SHA_256);
}
try {
// make sure we buffer the input
input = new BufferedInputStream(input, bufferSize);
byte[] buffer = new byte[bufferSize];
for (int bytesRead = input.read(buffer); bytesRead != -1; bytesRead = input.read(buffer)) {
output.write(buffer, 0, bytesRead);
numBytesCopied += bytesRead;
if (digestGenerator != null) {
digestGenerator.add(buffer, 0, bytesRead);
}
}
if (contentDetails != null) {//if we're calculating a digest as well
contentDetails.put(UPLOAD_FILE_SIZE, String.valueOf(numBytesCopied));
contentDetails.put(UPLOAD_SHA256, digestGenerator.getDigestString());
}
output.flush();
} catch (IOException ioe) {
throw new RuntimeException("Stream data cannot be copied", ioe);
} finally {
if (closeStreams) {
try {
output.close();
} catch (IOException ioe2) {
LOG.warn("Streams could not be closed", ioe2);
}
try {
input.close();
} catch (IOException ioe2) {
LOG.warn("Streams could not be closed", ioe2);
}
}
}
return numBytesCopied;
}
/** For Testing only<br><br>
*
* Writes the contents of a the Blob out to the stream passed in.
*
* @param stream non null stream where contents to be written to.
*/
@Override
public void writeBlobOutToStream(OutputStream stream, PackageBits bits, boolean closeStreams) {
if (stream == null) {
return; // no locate to write to
}
if ((bits == null) || (bits.getId() <= 0)) {
//then PackageBits instance passed in is insufficiently initialized.
LOG.warn("PackageBits insufficiently initialized. No data to write out.");
return;
}
Connection conn = null;
PreparedStatement ps = null;
ResultSet results = null;
try {
//open connection
conn = dataSource.getConnection();
//prepared statement for retrieval of Blob.bits
ps = conn.prepareStatement("SELECT BITS FROM " + PackageBits.TABLE_NAME + " WHERE ID = ?");
ps.setInt(1, bits.getId());
results = ps.executeQuery();
if (results.next()) {
//retrieve the Blob
Blob blob = results.getBlob(1);
//now copy the contents to the stream passed in
StreamUtil.copy(blob.getBinaryStream(), stream, closeStreams);
}
} catch (Exception ex) {
LOG.error("An error occurred while writing Blob contents out to stream :" + ex.getMessage());
ex.printStackTrace();
} finally {
JDBCUtil.safeClose(conn, ps, results);
}
}
@Override
public String createTemporaryContentHandle() {
try {
return File.createTempFile(TMP_FILE_PREFIX, TMP_FILE_SUFFIX, getTempDirectory()).getName();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private File getTempDirectory() {
String tempDirectoryPath = System.getProperty("java.io.tmpdir");
return new File(tempDirectoryPath);
}
@Override
public void uploadContentFragment(String temporaryContentHandle, byte[] fragment, int off, int len) {
File temporaryContentFile = getTemporaryContentFile(temporaryContentHandle);
ByteArrayInputStream inputStream = new ByteArrayInputStream(fragment, off, len);
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(temporaryContentFile, true); // append == true
StreamUtil.copy(inputStream, new BufferedOutputStream(fileOutputStream, 1024 * 32));
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} finally {
StreamUtil.safeClose(fileOutputStream);
}
}
@Override
public PackageVersion createPackageVersionWithDisplayVersion(Subject subject, String packageName,
int packageTypeId, String version, String displayVersion, Integer architectureId, String temporaryContentHandle) {
// Check permissions first
if (!authorizationManager.hasGlobalPermission(subject, Permission.MANAGE_CONTENT)) {
throw new PermissionException("User [" + subject.getName()
+ "] does not have permission to create package versions");
}
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream(getTemporaryContentFile(temporaryContentHandle));
return createPackageVersionWithDisplayVersion(subject, packageName, packageTypeId, version, displayVersion,
(null == architectureId) ? getNoArchitecture().getId() : architectureId, fileInputStream);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} finally {
StreamUtil.safeClose(fileInputStream);
}
}
@Override
@TransactionAttribute(TransactionAttributeType.SUPPORTS)
public File getTemporaryContentFile(String temporaryContentHandle) {
File tempDirectory = getTempDirectory();
File file = new File(tempDirectory, temporaryContentHandle);
if (!file.isFile()) {
throw new RuntimeException("Handle [" + temporaryContentHandle + "] does not denote a file");
}
return file;
}
}