/* * 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.core.plugin; import java.io.File; import java.util.Date; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.ejb.ConcurrencyManagement; import javax.ejb.ConcurrencyManagementType; import javax.ejb.LocalBean; import javax.ejb.Singleton; import javax.ejb.Startup; import javax.ejb.TransactionAttribute; import javax.ejb.TransactionAttributeType; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jboss.util.StringPropertyReplacer; import org.rhq.core.clientapi.agent.metadata.PluginMetadataManager; import org.rhq.core.clientapi.descriptor.AgentPluginDescriptorUtil; import org.rhq.core.util.MessageDigestGenerator; import org.rhq.core.util.exception.ThrowableUtil; import org.rhq.core.util.file.FileUtil; import org.rhq.enterprise.server.util.JMXUtil; import org.rhq.enterprise.server.util.LookupUtil; import org.rhq.enterprise.server.xmlschema.ServerPluginDescriptorUtil; /** * This looks at both the file system and the database for new agent and server plugins. * This does not perform any polling - it only performs scanning on-demand. It provides * some configuration settings that a timer could use to determine when to periodically * scan (see {@link #getScanPeriod()} for example. * * @author John Mazzitelli */ @Singleton @Startup @LocalBean @ConcurrencyManagement(ConcurrencyManagementType.BEAN) @TransactionAttribute(TransactionAttributeType.SUPPORTS) public class PluginDeploymentScanner implements PluginDeploymentScannerMBean { private Log log = LogFactory.getLog(PluginDeploymentScanner.class); /** time, in millis, between each scans */ private long scanPeriod = 300000L; /** where the user can copy agent or server plugins */ private File userPluginDir = null; /** what looks for new or changed agent plugins */ private AgentPluginScanner agentPluginScanner = new AgentPluginScanner(); /** what looks for new or changed agent plugins */ private ServerPluginScanner serverPluginScanner = new ServerPluginScanner(); // https://issues.jboss.org/browse/AS7-5343 forces us to change the attribute values as Strings // we then must convert ourselves. //public Long getScanPeriod() { public String getScanPeriod() { //return this.scanPeriod; return Long.toString(this.scanPeriod); } //public void setScanPeriod(Long ms) { public void setScanPeriod(String ms) { if (ms != null) { //this.scanPeriod = ms; this.scanPeriod = Long.parseLong(StringPropertyReplacer.replaceProperties(ms)); } else { this.scanPeriod = 300000L; } } //public File getUserPluginDir() { public String getUserPluginDir() { //return this.userPluginDir; if (this.userPluginDir == null) { return null; } return this.userPluginDir.getAbsolutePath(); } //public void setUserPluginDir(File dir) { public void setUserPluginDir(String dir) { if (dir == null) { return; } //this.userPluginDir = dir; this.userPluginDir = new File(StringPropertyReplacer.replaceProperties(dir)); } //public File getServerPluginDir() { public String getServerPluginDir() { //return this.serverPluginScanner.getServerPluginDir(); File dir = this.serverPluginScanner.getServerPluginDir(); if (dir == null) { return null; } return dir.getAbsolutePath(); } //public void setServerPluginDir(File dir) { public void setServerPluginDir(String dir) { if (dir == null) { return; } //this.serverPluginScanner.setServerPluginDir(dir); this.serverPluginScanner.setServerPluginDir(new File(StringPropertyReplacer.replaceProperties(dir))); } //public File getAgentPluginDir() { public String getAgentPluginDir() { //return this.agentPluginScanner.getAgentPluginDeployer().getPluginDir(); File dir = this.agentPluginScanner.getAgentPluginDeployer().getPluginDir(); if (dir == null) { return null; } return dir.getAbsolutePath(); } //public void setAgentPluginDir(File dir) { public void setAgentPluginDir(String dir) { if (dir == null) { return; } //this.agentPluginScanner.getAgentPluginDeployer().setPluginDir(dir); this.agentPluginScanner.getAgentPluginDeployer().setPluginDir( new File(StringPropertyReplacer.replaceProperties(dir))); } public PluginMetadataManager getPluginMetadataManager() { return this.agentPluginScanner.getAgentPluginDeployer().getPluginMetadataManager(); } private File getUserPluginDirAsFile() { return this.userPluginDir; } private File getAgentPluginDirAsFile() { return this.agentPluginScanner.getAgentPluginDeployer().getPluginDir(); } private File getServerPluginDirAsFile() { return this.serverPluginScanner.getServerPluginDir(); } public void start() throws Exception { return; } public void stop() { this.agentPluginScanner.getAgentPluginDeployer().stop(); return; } public void startDeployment() { // We are being called by the server's startup bean which essentially informs us that // the server's internal EJB/SLSBs are ready and can be called. This means we are allowed to start. // NOTE: Make sure we are called BEFORE the master plugin container is started! // setup our attributes - skip if the plugin dirs were already set (e.g. from test code) String upd = getUserPluginDir(); String apd = getAgentPluginDir(); String spd = getServerPluginDir(); // don't look up the core server mbean if we don't need do if (upd == null || apd == null || spd == null) { File homeDir = LookupUtil.getCoreServer().getInstallDir(); File earDir = LookupUtil.getCoreServer().getEarDeploymentDir(); if (upd == null) { upd = new File(homeDir, "plugins").getAbsolutePath(); setUserPluginDir(upd); } if (apd == null) { apd = new File(earDir, "rhq-downloads/rhq-plugins").getAbsolutePath(); setAgentPluginDir(apd); } if (spd == null) { spd = new File(earDir, "rhq-serverplugins").getAbsolutePath(); setServerPluginDir(spd); } } log.info("user plugin dir=" + upd); log.info("agent plugin dir=" + apd); log.info("server plugin dir=" + spd); // This will check to see if there are any agent plugin records in the database // that do not have content associated with them and if so, will stream // the content from the file system to the database. This is needed only // in the case when this server has recently been upgraded from an old // version of the software that did not originally have content stored in the DB. // Once we do that, we can start the agent plugin deployer. try { this.agentPluginScanner.fixMissingAgentPluginContent(); this.agentPluginScanner.getAgentPluginDeployer().start(); } catch (Exception e) { throw new RuntimeException("Cannot start plugin deployment scanner properly", e); } this.agentPluginScanner.getAgentPluginDeployer().startDeployment(); // do the initial scan now try { scanAndRegister(); } catch (Throwable t) { log.error("Scan failed. Cause: " + ThrowableUtil.getAllMessages(t)); if (log.isDebugEnabled()) { log.debug("Scan failure stack trace follows:", t); } } return; } public synchronized void scan() throws Exception { // The user directory is a simple location for the user to put all plugins in. // It makes it easy for the user to know where to put the plugins without // having to know the internal location for the real plugins under the ear. // Now we move the user's plugins to their real location in the ear. scanUserDirectory(); // scan for agent plugins this.agentPluginScanner.agentPluginScan(); // scan for server plugins this.serverPluginScanner.serverPluginScan(); return; } public synchronized void scanAndRegister() throws Exception { // do the scan first to find any new/updated plugins scan(); // now tell the agent plugin scanner to register plugins that it determined needs to be registered this.agentPluginScanner.registerAgentPlugins(); // tell the server plugin scanner the same this.serverPluginScanner.registerServerPlugins(); } /** * Take the plugins placed in the user directory, and copy them to their apprpriate places * in the server. */ private void scanUserDirectory() { File userDir = getUserPluginDirAsFile(); // if (userDir == null || !userDir.isDirectory()) { return; // not configured for a user directory, just return immediately and do nothing } File[] listFiles = userDir.listFiles(); if (listFiles == null || listFiles.length == 0) { return; // nothing to do } for (File file : listFiles) { File destinationDirectory; boolean isJarLess = file.getName().endsWith("-rhq-plugin.xml"); if (file.getName().endsWith(".jar") || isJarLess ) { try { if (!isJarLess && null == AgentPluginDescriptorUtil.loadPluginDescriptorFromUrl(file.toURI().toURL())) { throw new NullPointerException("no xml descriptor found in jar"); } destinationDirectory = getAgentPluginDirAsFile(); // } catch (Exception e) { try { log.debug("[" + file.getAbsolutePath() + "] is not an agent plugin jar (Cause: " + ThrowableUtil.getAllMessages(e) + "). Will see if its a server plugin jar"); if (null == ServerPluginDescriptorUtil.loadPluginDescriptorFromUrl(file.toURI().toURL())) { throw new NullPointerException("no xml descriptor found in jar"); } destinationDirectory = getServerPluginDirAsFile(); // } catch (Exception e1) { // skip it, doesn't look like a valid plugin jar File fixmeFile = new File(file.getAbsolutePath() + ".fixme"); boolean renamed = file.renameTo(fixmeFile); log.warn("Does not look like [" + (renamed ? fixmeFile : file).getAbsolutePath() + "] is a plugin jar -(Cause: " + ThrowableUtil.getAllMessages(e1) + "). It will be ignored. Please fix that file or remove it."); continue; } } try { String fileMd5 = MessageDigestGenerator.getDigestString(file); File realPluginFile = new File(destinationDirectory, file.getName()); String realPluginFileMd5 = null; if (realPluginFile.exists()) { realPluginFileMd5 = MessageDigestGenerator.getDigestString(realPluginFile); } if (!fileMd5.equals(realPluginFileMd5)) { if (file.lastModified() > realPluginFile.lastModified()) { FileUtil.copyFile(file, realPluginFile); boolean succeeded = realPluginFile.setLastModified(file.lastModified()); if (!succeeded) { log.error("Failed to set mtime to [" + new Date(file.lastModified()) + "] on file [" + realPluginFile + "]."); } String tmp; if (!isJarLess) tmp = "jar"; else tmp = "descriptor"; log.info("Found plugin " + tmp + " at [" + file.getAbsolutePath() + "] and placed it at [" + realPluginFile.getAbsolutePath() + "]"); } } else { log.info("Found a plugin at [" + file.getAbsolutePath() + "], which is the same as the existing one. It will be ignored"); } boolean deleted = file.delete(); if (!deleted) { log.info("The plugin jar found at[" + file.getAbsolutePath() + "] has been processed and can be deleted. It failed to get deleted, " + "so it may get processed again. You should delete it manually now."); } } catch (Exception e) { log.error("Failed to process plugin [" + file.getAbsolutePath() + "], ignoring it", e); } } } return; } @PostConstruct private void init() { JMXUtil.registerMBean(this, OBJECT_NAME); setScanPeriod("${rhq.server.plugin-scan-period-ms:300000}"); } @PreDestroy private void destroy() { stop(); JMXUtil.unregisterMBeanQuietly(OBJECT_NAME); } }