package org.rhq.enterprise.server.resource.metadata; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.net.URL; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; 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.NoResultException; 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.quartz.JobDetail; import org.quartz.ObjectAlreadyExistsException; import org.quartz.SchedulerException; import org.quartz.Trigger; import org.rhq.core.clientapi.agent.metadata.PluginDependencyGraph; import org.rhq.core.clientapi.agent.metadata.PluginMetadataManager; import org.rhq.core.clientapi.descriptor.AgentPluginDescriptorUtil; import org.rhq.core.clientapi.descriptor.plugin.PluginDescriptor; import org.rhq.core.domain.auth.Subject; import org.rhq.core.domain.authz.Permission; import org.rhq.core.domain.cloud.Server; import org.rhq.core.domain.criteria.Criteria; import org.rhq.core.domain.criteria.PluginCriteria; import org.rhq.core.domain.criteria.ResourceTypeCriteria; import org.rhq.core.domain.plugin.CannedGroupAddition; import org.rhq.core.domain.plugin.CannedGroupExpression; import org.rhq.core.domain.plugin.Plugin; import org.rhq.core.domain.plugin.PluginDeploymentType; import org.rhq.core.domain.plugin.PluginStatusType; import org.rhq.core.domain.resource.ResourceCategory; import org.rhq.core.domain.resource.ResourceType; import org.rhq.core.domain.util.PageList; import org.rhq.core.util.file.FileUtil; 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.auth.SubjectManagerLocal; import org.rhq.enterprise.server.authz.RequiredPermission; import org.rhq.enterprise.server.content.ContentManagerLocal; import org.rhq.enterprise.server.core.AgentManagerLocal; import org.rhq.enterprise.server.core.plugin.PluginAdditionsReader; import org.rhq.enterprise.server.core.plugin.PluginDeploymentScannerMBean; import org.rhq.enterprise.server.inventory.InventoryManagerLocal; import org.rhq.enterprise.server.resource.ResourceManagerLocal; import org.rhq.enterprise.server.resource.ResourceTypeManagerLocal; import org.rhq.enterprise.server.resource.group.definition.GroupDefinitionManagerLocal; import org.rhq.enterprise.server.scheduler.SchedulerLocal; 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; @Stateless public class PluginManagerBean implements PluginManagerLocal, PluginManagerRemote { private final Log log = LogFactory.getLog(PluginManagerBean.class); @javax.annotation.Resource(name = "RHQ_DS", mappedName = RHQConstants.DATASOURCE_JNDI_NAME) private DataSource dataSource; @PersistenceContext(unitName = RHQConstants.PERSISTENCE_UNIT_NAME) private EntityManager entityManager; @EJB private ResourceMetadataManagerLocal resourceMetadataManager; @EJB private PluginManagerLocal pluginMgr; @EJB private InventoryManagerLocal inventoryMgr; @EJB private ResourceTypeManagerLocal resourceTypeMgr; @EJB private ResourceManagerLocal resourceMgr; @EJB private SubjectManagerLocal subjectMgr; @EJB private ContentManagerLocal contentManager; @EJB private AgentManagerLocal agentManager; @EJB private SchedulerLocal scheduler; @Override public Plugin getPlugin(String name) { Query query = entityManager.createNamedQuery(Plugin.QUERY_FIND_BY_NAME); query.setParameter("name", name); Plugin result = null; try { result = (Plugin) query.getSingleResult(); } catch (NoResultException e) { result = null; } return result; } @Override public boolean isReadyForPurge(Plugin plugin) { int resourceTypeCount = getResourceTypeCount(plugin); if (resourceTypeCount > 0) { if (log.isDebugEnabled()) { log.debug(plugin + " is not ready to be purged. It still has " + resourceTypeCount + " resource types in the database."); } return false; } //check that all the servers have acked the deletion Plugin inDbPlugin = entityManager.find(Plugin.class, plugin.getId()); @SuppressWarnings("unchecked") List<Server> allServers = entityManager.createNamedQuery(Server.QUERY_FIND_ALL).getResultList(); for (Server s : allServers) { if (!inDbPlugin.getServersAcknowledgedDelete().contains(s)) { if (log.isDebugEnabled()) { log.debug(plugin + " is not ready to be purged. Server " + s + " has not acknowledged it knows about its deletion."); } return false; } } return true; } private int getResourceTypeCount(Plugin plugin) { // this will get all types, even those deleted and ignored ResourceTypeCriteria criteria = new ResourceTypeCriteria(); criteria.addFilterPluginName(plugin.getName()); criteria.setRestriction(Criteria.Restriction.COUNT_ONLY); criteria.addFilterDeleted(null); criteria.addFilterIgnored(null); criteria.setStrict(true); PageList<ResourceType> types = resourceTypeMgr.findResourceTypesByCriteria(subjectMgr.getOverlord(), criteria); return types.getTotalSize(); } @Override public void purgePlugins(List<Plugin> plugins) { for(Plugin p : plugins) { Plugin inDb = entityManager.find(Plugin.class, p.getId()); inDb.getServersAcknowledgedDelete().clear(); entityManager.remove(inDb); } if (log.isDebugEnabled()) { log.debug("The following plugins were purged from the database: " + plugins); } } @Override public List<Plugin> getPlugins() { return entityManager.createNamedQuery(Plugin.QUERY_FIND_ALL).getResultList(); } @SuppressWarnings("unchecked") @Override public List<Plugin> getInstalledPlugins() { Query q = entityManager.createNamedQuery(Plugin.QUERY_FIND_ALL_INSTALLED); return q.getResultList(); } @Override public List<Plugin> findAllDeletedPlugins() { return entityManager.createNamedQuery(Plugin.QUERY_FIND_ALL_DELETED).getResultList(); } @SuppressWarnings("unchecked") @Override public List<Plugin> getAllPluginsById(List<Integer> pluginIds) { if (pluginIds == null || pluginIds.size() == 0) { return new ArrayList<Plugin>(); // nothing to do } Query query = entityManager.createNamedQuery(Plugin.QUERY_FIND_ALL_BY_IDS); query.setParameter("ids", pluginIds); return query.getResultList(); } @SuppressWarnings("unchecked") @Override public List<Plugin> getPluginsByResourceTypeAndCategory(String resourceTypeName, ResourceCategory resourceCategory) { Query query = entityManager.createNamedQuery(Plugin.QUERY_FIND_BY_RESOURCE_TYPE_AND_CATEGORY); query.setParameter("resourceTypeName", resourceTypeName); query.setParameter("resourceCategory", resourceCategory); List<Plugin> results = query.getResultList(); return results; } @RequiredPermission(Permission.MANAGE_SETTINGS) @Override public void enablePlugins(Subject subject, List<Integer> pluginIds) throws Exception { if (pluginIds == null || pluginIds.size() == 0) { return; // nothing to do } // we need to make sure that if a plugin is enabled, all of its dependencies are enabled PluginDependencyGraph graph = getPluginMetadataManager().buildDependencyGraph(); List<Plugin> allPlugins = getInstalledPlugins(); Set<String> pluginsThatNeedToBeEnabled = new HashSet<String>(); for (Integer pluginId : pluginIds) { Plugin plugin = getPluginFromListById(allPlugins, pluginId.intValue()); if (plugin != null) { Collection<String> dependencyNames = graph.getAllDependencies(plugin.getName()); for (String dependencyName : dependencyNames) { Plugin dependencyPlugin = getPluginFromListByName(allPlugins, dependencyName); if (dependencyPlugin != null && !dependencyPlugin.isEnabled() && !pluginIds.contains(Integer.valueOf(dependencyPlugin.getId()))) { pluginsThatNeedToBeEnabled.add(dependencyPlugin.getDisplayName()); // this isn't enabled and isn't getting enabled, but it needs to be } } } } if (!pluginsThatNeedToBeEnabled.isEmpty()) { throw new IllegalArgumentException("You must enable the following plugin dependencies also: " + pluginsThatNeedToBeEnabled); } // everything is OK, we can enable them for (Integer pluginId : pluginIds) { setPluginEnabledFlag(subject, pluginId, true); } return; } @Override @RequiredPermission(Permission.MANAGE_SETTINGS) public void disablePlugins(Subject subject, List<Integer> pluginIds) throws Exception { if (pluginIds == null || pluginIds.size() == 0) { return; // nothing to do } // we need to make sure that if a plugin is disabled, no other plugins that depend on it are enabled PluginDependencyGraph graph = getPluginMetadataManager().buildDependencyGraph(); List<Plugin> allPlugins = getInstalledPlugins(); Set<String> pluginsThatNeedToBeDisabled = new HashSet<String>(); for (Integer pluginId : pluginIds) { Plugin plugin = getPluginFromListById(allPlugins, pluginId.intValue()); if (plugin != null) { Collection<String> dependentNames = graph.getAllDependents(plugin.getName()); for (String dependentName : dependentNames) { Plugin dependentPlugin = getPluginFromListByName(allPlugins, dependentName); if (dependentPlugin != null && dependentPlugin.isEnabled() && !pluginIds.contains(Integer.valueOf(dependentPlugin.getId()))) { pluginsThatNeedToBeDisabled.add(dependentPlugin.getDisplayName()); // this isn't disabled and isn't getting disabled, but it needs to be } } } } if (!pluginsThatNeedToBeDisabled.isEmpty()) { throw new IllegalArgumentException("You must disable the following dependent plugins also: " + pluginsThatNeedToBeDisabled); } // everything is OK, we can disable them for (Integer pluginId : pluginIds) { setPluginEnabledFlag(subject, pluginId, false); } return; } @Override @RequiredPermission(Permission.MANAGE_SETTINGS) @TransactionAttribute(TransactionAttributeType.NEVER) public void deletePlugins(Subject subject, List<Integer> pluginIds) throws Exception { if (pluginIds.isEmpty()) { return; } PluginDependencyGraph graph = getPluginMetadataManager().buildDependencyGraph(); List<Plugin> allPlugins = getInstalledPlugins(); Set<String> pluginsToDelete = new HashSet<String>(); for (Integer pluginId : pluginIds) { Plugin plugin = getPluginFromListById(allPlugins, pluginId.intValue()); if (plugin != null && plugin.getStatus().equals(PluginStatusType.INSTALLED)) { Collection<String> dependentNames = graph.getAllDependents(plugin.getName()); for (String dependentName : dependentNames) { Plugin dependentPlugin = getPluginFromListByName(allPlugins, dependentName); if (dependentPlugin != null && dependentPlugin.isEnabled() && !pluginIds.contains(Integer.valueOf(dependentPlugin.getId()))) { pluginsToDelete.add(dependentPlugin.getDisplayName()); } } } } if (!pluginsToDelete.isEmpty()) { throw new IllegalArgumentException("You must delete the following dependent plugins also: " + pluginsToDelete); } // In order to avoid a large transaction issue, when deleting one or more plugins with possibly quite a large // resource population, perform in multiple transactions, being mindful of consistency. Because of plugin // dependency, it's important that minimally all of the plugins and related types are *marked* for // deletion in a single transaction. This prevents inconsistent plugin state and the method is considered a // success if we commit that transaction. After that, again trying to avoid an overly large umbrella // transaction, we mark the relevant resources for uninventory. If that fails it will be detected by the // PurgeResourceTypesJob and rectified later. List<Plugin> plugins = pluginMgr.getAllPluginsById(pluginIds); // Do this in its own transaction to ensure everything gets marked. pluginMgr.markPluginsDeleted(subject, plugins); // Now, try and uninventory the resource of doomed plugin types try { for (Plugin plugin : plugins) { deleteResourcesForPlugin(subject, plugin); } } catch (Throwable t) { log.warn( "Failed to uninventory all resources of deleted plugins. This should fix itself automatically when the PurgeResourceTypsJob executes.", t); } GroupDefinitionManagerLocal groupDefMgr = LookupUtil.getGroupDefinitionManager(); for (Plugin plugin : plugins) { groupDefMgr.updateGroupsByCannedExpressions(plugin.getName(), null); } } private void deleteResourcesForPlugin(Subject subject, Plugin plugin) throws Exception { // Uninventory all of the top level resources for the plugin's deleted types. The children go away automatically ResourceTypeCriteria criteria = new ResourceTypeCriteria(); criteria.setStrict(true); criteria.addFilterPluginName(plugin.getName()); criteria.addFilterDeleted(true); // get all deleted types ... criteria.addFilterIgnored(null); // ... whether they are ignored or not criteria.addFilterParentResourceTypesEmpty(true); criteria.clearPaging(); List<ResourceType> deletedServerTypes = resourceTypeMgr.findResourceTypesByCriteria(subject, criteria); // Do this type by type in an effort to keep chunks smaller. for (ResourceType deletedServerType : deletedServerTypes) { deleteResourcesForType(subject, deletedServerType); } } private void deleteResourcesForType(Subject subject, ResourceType type) throws Exception { List<Integer> typeIds = new ArrayList<Integer>(1); typeIds.add(type.getId()); List<Integer> resourceIds = resourceMgr.findIdsByTypeIds(typeIds); for (Integer resourceId : resourceIds) { resourceMgr.uninventoryResourceInNewTransaction(resourceId); } } @Override @RequiredPermission(Permission.MANAGE_SETTINGS) public void markPluginsDeleted(Subject subject, List<Plugin> plugins) throws Exception { log.debug(subject + " preparing to delete the following plugins: " + plugins); for (Plugin plugin : plugins) { if (plugin.getStatus().equals(PluginStatusType.INSTALLED)) { long startTime = System.currentTimeMillis(); List<Integer> resourceTypeIds = resourceTypeMgr.getResourceTypeIdsByPlugin(plugin.getName()); inventoryMgr.markTypesDeleted(resourceTypeIds, false); plugin.setStatus(PluginStatusType.DELETED); entityManager.merge(plugin); long endTime = System.currentTimeMillis(); log.debug("Deleted " + plugin + " in " + (endTime - startTime) + " ms"); } else { log.debug("Skipping " + plugin + ". It is already deleted."); } } } @Override public List<PluginStats> getPluginStats(List<Integer> pluginIds) { List<PluginStats> stats = new ArrayList<PluginStats>(); List<Plugin> plugins = getAllPluginsById(pluginIds); for (Plugin plugin : plugins) { List<Integer> resourceTypeIds = resourceTypeMgr.getResourceTypeIdsByPlugin(plugin.getName()); Integer resourceCount = resourceMgr.getResourceCount(resourceTypeIds); stats.add(new PluginStats(plugin, resourceTypeIds.size(), resourceCount)); } return stats; } @RequiredPermission(Permission.MANAGE_SETTINGS) @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) @Override public void setPluginEnabledFlag(Subject subject, int pluginId, boolean enabled) throws Exception { Query q = entityManager.createNamedQuery(Plugin.UPDATE_PLUGIN_ENABLED_BY_ID); q.setParameter("id", pluginId); q.setParameter("enabled", Boolean.valueOf(enabled)); q.executeUpdate(); log.info((enabled ? "Enabling" : "Disabling") + " plugin [" + pluginId + "]"); return; } @Override public File getPluginDropboxDirectory() { File dir = new File(LookupUtil.getPluginDeploymentScanner().getUserPluginDir()); return dir; } private Plugin getPluginFromListById(List<Plugin> plugins, int id) { for (Plugin plugin : plugins) { if (id == plugin.getId()) { return plugin; } } return null; } private Plugin getPluginFromListByName(List<Plugin> plugins, String name) { for (Plugin plugin : plugins) { if (name.equals(plugin.getName())) { return plugin; } } return null; } // Start with no transaction so we can control the transactional boundaries. This is important for a // few reasons. Registering the plugin and removing obsolete types are performed in different, subsequent, // transactions. The registration may update types, and that locks various rows of the database. Those rows // must be unlocked before obsolete type removal. Type removal executes (resource) bulk delete under the covers, // and that will deadlock with the rows locked by the type update (at least in oracle) if performed in the same // transaction. Furthermore, as mentioned, obsolete type removal removes resources of the obsolete type. We // need to avoid an umbrella transaction for the type removal because large inventories of obsolete resources // will generate very large transactions. Potentially resulting in timeouts or other issues. @TransactionAttribute(TransactionAttributeType.NEVER) @Override public void registerPlugin(Plugin plugin, PluginDescriptor pluginDescriptor, File pluginFile, boolean forceUpdate) throws Exception { if (isDeleted(plugin)) { String msg = "A deleted version of " + plugin + " already exists in the database. The plugin cannot be " + "installed until the deleted version is purged from the database (which should happen a couple of" + " minutes after all servers acknowledged the plugin was deleted). Especially, the plugin won't be" + " purged if ANY of the servers in the HA cloud have been down at the point in time the plugin was" + " deleted and haven't gone back up yet."; log.warn(msg); throw new IllegalStateException(msg); } log.debug("Registering " + plugin + "..."); long startTime = System.currentTimeMillis(); boolean newOrUpdated = pluginMgr.installPluginJar(plugin, pluginDescriptor, pluginFile); boolean typesUpdated = registerPluginTypes(plugin.getName(), pluginDescriptor, newOrUpdated, forceUpdate); if (typesUpdated) { // There may be other types in other plugins that extended from this plugin (the "embedded" extension mechanism). // In this case, we have to re-deploy those plugins so those types get recreated with the new metadata. // We do the same thing with the extended plugins that we will do with our passed-in "parent" plugin, that is, // we register the extended plugins' types (we'll force it to update types since we know something changed in the parent) // and then we'll remove any obsoleted types from the extended plugins. PluginMetadataManager metadataManager = getPluginMetadataManager(); Map<String, PluginDescriptor> extensions = metadataManager.getEmbeddedExtensions(plugin.getName()); if (extensions != null && extensions.size() > 0) { for (Map.Entry<String, PluginDescriptor> entry : extensions.entrySet()) { String extPluginName = entry.getKey(); PluginDescriptor extPluginDescriptor = entry.getValue(); log.debug("Plugin [" + extPluginName + "] will be re-registered because it embeds types from plugin [" + plugin.getName() + "]"); registerPluginTypes(extPluginName, extPluginDescriptor, false, true); resourceMetadataManager.removeObsoleteTypes(subjectMgr.getOverlord(), extPluginName, metadataManager); } } // now remove any obsolete types from the newly registered plugin resourceMetadataManager.removeObsoleteTypes(subjectMgr.getOverlord(), plugin.getName(), metadataManager); resourceMetadataManager.removeObsoleteSubCategories(subjectMgr.getOverlord(), null, null); } long endTime = System.currentTimeMillis(); log.debug("Finished registering " + plugin + " in " + (endTime - startTime) + " ms"); } private boolean isDeleted(Plugin plugin) { for (Plugin deletedPlugins : findAllDeletedPlugins()) { if (deletedPlugins.equals(plugin)) { return true; } } return false; } @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) @Override public boolean installPluginJar(Plugin newPlugin, PluginDescriptor pluginDescriptor, File pluginFile) throws Exception { Plugin existingPlugin = getPlugin(newPlugin.getName()); boolean newOrUpdated = (null == existingPlugin); if (existingPlugin != null) { Plugin obsolete = AgentPluginDescriptorUtil.determineObsoletePlugin(newPlugin, existingPlugin); if (obsolete == existingPlugin) { // yes, use == for reference equality newOrUpdated = true; } newPlugin.setId(existingPlugin.getId()); newPlugin.setEnabled(existingPlugin.isEnabled()); } // If this is a brand new plugin, it gets "updated" too. if (newOrUpdated) { if (newPlugin.getDisplayName() == null) { newPlugin.setDisplayName(newPlugin.getName()); } newPlugin = updatePluginExceptContent(newPlugin); if (pluginFile != null) { entityManager.flush(); streamPluginFileContentToDatabase(newPlugin.getId(), pluginFile); } log.debug("Updated plugin entity [" + newPlugin + "]"); } return newOrUpdated; } // Should not be in a Tx here, we need to update each type in its own Tx so that no Tx is too large. Updating // a type can be intensive at scale. private boolean registerPluginTypes(String newPluginName, PluginDescriptor pluginDescriptor, boolean newOrUpdated, boolean forceUpdate) throws Exception { boolean typesUpdated = false; PluginMetadataManager metadataManager = getPluginMetadataManager(); if (newOrUpdated || forceUpdate || !metadataManager.getPluginNames().contains(newPluginName)) { Set<ResourceType> rootResourceTypes = metadataManager.loadPlugin(pluginDescriptor); if (rootResourceTypes == null) { throw new Exception("Failed to load plugin [" + newPluginName + "]."); } if (newOrUpdated || forceUpdate) { // Only merge the plugin's ResourceTypes into the DB if the plugin is new or updated or we were forced to resourceMetadataManager.updateTypes(rootResourceTypes); typesUpdated = true; } } return typesUpdated; } @Override @RequiredPermission(Permission.MANAGE_SETTINGS) @TransactionAttribute(TransactionAttributeType.NEVER) public void update(Subject subject) throws Exception { PluginDeploymentScannerMBean scanner = LookupUtil.getPluginDeploymentScanner(); scanner.scanAndRegister(); } @Override @RequiredPermission(Permission.MANAGE_SETTINGS) public String schedulePluginUpdateOnAgents(Subject subject, long delayInMilliseconds) throws Exception { JobDetail jobDetail = UpdatePluginsOnAgentsJob.getJobDetail(); Trigger trigger = QuartzUtil.getFireOnceOffsetTrigger(jobDetail, delayInMilliseconds); try { scheduler.scheduleJob(jobDetail, trigger); return jobDetail.getName(); } catch (ObjectAlreadyExistsException e) { //well, there already is a plugin update job scheduled, so let's just not add another one. log.debug("A request to update plugins on agents seems to already be scheduled." + " Ignoring the current request with the error message: " + e.getMessage()); throw e; } } @Override @RequiredPermission(Permission.MANAGE_SETTINGS) public boolean isPluginUpdateOnAgentsFinished(Subject subject, String handle) { try { return scheduler.getJobDetail(handle, UpdatePluginsOnAgentsJob.class.getName()) == null; } catch (SchedulerException e) { if (log.isDebugEnabled()) { log.warn("Failed to retrieve job details while checking for active plugin update schedule, code: " + e.getErrorCode(), e); } else { log.warn("Failed to retrieve job details while checking for active plugin update schedule, code: " + e.getErrorCode() + ", message: " + e.getMessage()); } return false; } } @Override @RequiredPermission(Permission.MANAGE_SETTINGS) @TransactionAttribute(TransactionAttributeType.NEVER) public List<Plugin> deployUsingBytes(Subject subject, String pluginJarName, byte[] pluginJarBytes) throws Exception { File base = getPluginDropboxDirectory(); File targetFile = new File(base, pluginJarName); FileOutputStream out = new FileOutputStream(targetFile); ByteArrayInputStream in = new ByteArrayInputStream(pluginJarBytes); StreamUtil.copy(in, out, true); return updateAndDetectChanges(subject); } @Override @RequiredPermission(Permission.MANAGE_SETTINGS) @TransactionAttribute(TransactionAttributeType.NEVER) public List<Plugin> deployUsingContentHandle(Subject subject, String pluginJarName, String handle) throws Exception { File pluginJar = contentManager.getTemporaryContentFile(handle); File base = getPluginDropboxDirectory(); FileUtil.copyFile(pluginJar, new File(base, pluginJarName)); return updateAndDetectChanges(subject); } @Override @RequiredPermission(Permission.MANAGE_SETTINGS) public PageList<Plugin> findPluginsByCriteria(Subject subject, PluginCriteria criteria) { CriteriaQueryGenerator generator = new CriteriaQueryGenerator(subject, criteria); CriteriaQueryRunner<Plugin> queryRunner = new CriteriaQueryRunner<Plugin>(criteria, generator, entityManager); return queryRunner.execute(); } private List<Plugin> updateAndDetectChanges(Subject subject) throws Exception { List<Plugin> before = getPlugins(); update(subject); List<Plugin> after = getPlugins(); for (Plugin p : before) { after.remove(p); } return after; } public void acknowledgeDeletedPluginsBy(int serverId) { Query q = entityManager.createNamedQuery(Plugin.QUERY_UNACKED_DELETED_PLUGINS); q.setParameter("serverId", serverId); @SuppressWarnings("unchecked") List<Plugin> plugins = q.getResultList(); Server server = entityManager.find(Server.class, serverId); for (Plugin p : plugins) { p.getServersAcknowledgedDelete().add(server); entityManager.merge(p); } } private Plugin updatePluginExceptContent(Plugin plugin) throws Exception { // this method is here because we need a way to update the plugin's information // without blowing away the content data. Because we do not want to load the // content blob in memory, the plugin's content field will be null - if we were // to entityManager.merge that plugin POJO, it would null out that blob column. if (plugin.getId() == 0) { entityManager.persist(plugin); } else { // update all the fields except content Plugin pluginEntity = entityManager.getReference(Plugin.class, plugin.getId()); pluginEntity.setName(plugin.getName()); pluginEntity.setPath(plugin.getPath()); pluginEntity.setDisplayName(plugin.getDisplayName()); pluginEntity.setEnabled(plugin.isEnabled()); pluginEntity.setStatus(plugin.getStatus()); pluginEntity.setMd5(plugin.getMD5()); pluginEntity.setVersion(plugin.getVersion()); pluginEntity.setAmpsVersion(plugin.getAmpsVersion()); pluginEntity.setDeployment(plugin.getDeployment()); pluginEntity.setDescription(plugin.getDescription()); pluginEntity.setHelp(plugin.getHelp()); pluginEntity.setMtime(plugin.getMtime()); try { entityManager.flush(); // make sure we push this out to the DB now } catch (Exception e) { throw new Exception("Failed to update a plugin that matches [" + plugin + "]"); } } return plugin; } /** * This will write the contents of the given plugin file to the database. * This will assume the MD5 in the database is already correct, so this * method will not take the time to calculate the MD5 again. * * @param id the id of the plugin whose content is being updated * @param file the plugin file whose content will be streamed to the database * * @throws Exception on failure to update the plugin's content */ private void streamPluginFileContentToDatabase(int id, File file) throws Exception { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; FileInputStream fis = new FileInputStream(file); try { conn = this.dataSource.getConnection(); ps = conn.prepareStatement("UPDATE " + Plugin.TABLE_NAME + " SET CONTENT = ? WHERE ID = ?"); ps.setBinaryStream(1, new BufferedInputStream(fis), (int) file.length()); ps.setInt(2, id); int updateResults = ps.executeUpdate(); if (updateResults != 1) { throw new Exception("Failed to update content for plugin [" + id + "] from [" + file + "]"); } } finally { JDBCUtil.safeClose(conn, ps, rs); StreamUtil.safeClose(fis); } return; } /** * Returns the metadata manager that will be used to obtain the resource types from * descriptors. * * @return metadata manager */ private PluginMetadataManager getPluginMetadataManager() { return LookupUtil.getPluginDeploymentScanner().getPluginMetadataManager(); } /** * gets canned expressions from all plugins by direct reading and parsing additional descriptors; */ public List<CannedGroupExpression> getCannedGroupExpressions() { ArrayList<CannedGroupExpression> list = new ArrayList<CannedGroupExpression>(); String pluginDir = LookupUtil.getPluginDeploymentScanner().getAgentPluginDir(); long now = System.currentTimeMillis(); log.debug("Reading canned expressions from all agent plugin jars"); for (Plugin plugin : this.getInstalledPlugins()) { if (plugin.getDeployment().equals(PluginDeploymentType.AGENT)) { File pluginFile = new File(pluginDir, plugin.getPath()); try { URL pluginUrl = pluginFile.toURI().toURL(); CannedGroupAddition addition = PluginAdditionsReader.getCannedGroupsAddition(pluginUrl, plugin.getName()); if (addition != null) { list.addAll(addition.getExpressions()); } } catch (Exception e) { log.error("Failed to parse plugin addition found in plugin [" + pluginFile.getAbsolutePath() + "]",e); } } } if (log.isDebugEnabled()) { log.debug("Reading "+list.size()+" canned expressions from all plugins took "+(System.currentTimeMillis()-now)+"ms"); } return list; } }