/* * RHQ Management Platform * Copyright (C) 2005-2014 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.core.pc.inventory; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.rhq.core.clientapi.agent.PluginContainerException; import org.rhq.core.clientapi.agent.metadata.PluginMetadataManager; import org.rhq.core.domain.resource.Resource; import org.rhq.core.domain.resource.ResourceType; import org.rhq.core.pc.PluginContainer; /** * Provides methods to read and write inventory data to a file. * * @author John Mazzitelli */ public class InventoryFile { private static final Log log = LogFactory.getLog(InventoryFile.class); private final File inventoryFile; private Resource platform; private Map<String, ResourceContainer> resourceContainers; // keyed on UUID private final InventoryManager inventoryManager; /** * Constructor for {@link InventoryFile} that will read and write inventory data to the given file. * * @param inventoryFile the path to the inventory.dat file */ public InventoryFile(File inventoryFile, InventoryManager inventoryManager) { this.inventoryFile = inventoryFile; this.inventoryManager = inventoryManager; } /** * Constructor for {@link InventoryFile} that will read and write inventory data to the given file. * * @param inventoryFile the path to the inventory.dat file */ public InventoryFile(File inventoryFile) { this(inventoryFile, PluginContainer.getInstance().getInventoryManager()); } public File getInventoryFile() { return inventoryFile; } /** * Returns the platform resource found in the inventory file. * * <p>This will return <code>null</code> if the file has not yet been {@link #loadInventory() loaded}, initially * {@link #storeInventory(Resource, Map) written to} or if an error occurred that did not allow the inventory file * to be fully loaded successfully.</p> * * @return platform resource in inventory file */ public Resource getPlatform() { return platform; } /** * Returns the map of {@link ResourceContainer resource containers} (keyed on their UUIDs) found in the inventory * file. * * <p>This will return <code>null</code> if the file has not yet been {@link #loadInventory() loaded}, initially * {@link #storeInventory(Resource, Map) written to} or if an error occurred that did not allow the inventory file * to be fully loaded successfully.</p> * * @return platform resource in inventory file */ public Map<String, ResourceContainer> getResourceContainers() { return resourceContainers; } /** * Reads in the inventory found in the file. Once this returns, {@link #getPlatform()} and * {@link #getResourceContainers()} will return non-<code>null</code> objects as found in the file. * * @throws PluginContainerException if some error occurred that did not allow this method to fully load the * inventory */ public void loadInventory() throws PluginContainerException { FileInputStream fis = null; try { fis = new FileInputStream(inventoryFile); ObjectInputStream ois = new ObjectInputStream(fis); // this list will contain UUIDs of resources that we should ignore usually due to disabled plugins Set<String> uuidsToIgnore = new HashSet<String>(); this.platform = (Resource) ois.readObject(); connectTypes(this.platform, uuidsToIgnore); this.resourceContainers = (Map<String, ResourceContainer>) ois.readObject(); for (ResourceContainer resourceContainer : this.resourceContainers.values()) { connectTypes(resourceContainer.getResource(), uuidsToIgnore); } for (String uuidToIgnore : uuidsToIgnore) { this.resourceContainers.remove(uuidToIgnore); } // purge all resources from disabled plugins removeIgnoredResourcesFromChildren(this.platform, uuidsToIgnore); return; } catch (Exception e) { throw new PluginContainerException("Cannot load inventory file: " + inventoryFile, e); } finally { if (fis != null) { try { fis.close(); } catch (Exception e) { } } } } private void removeIgnoredResourcesFromChildren(Resource resource, Set<String> uuidsToIgnore) { // nothing to ignore, just return if (null == uuidsToIgnore || uuidsToIgnore.isEmpty()) { return; } // Note that 'children' will likely be a CopyOnWriteArraySet and will not have a modifiable Iterator // implementation. So, alter the set only with a supported method, and only once to minimize recreation // of the backing Array. Set<Resource> children = inventoryManager.getContainerChildren(resource); Set<Resource> childrenToRemove = null; Set<String> uuidsToRemove = null; // no children, just return if (children.isEmpty()) { return; } // loop through the children, collecting those that are ignored and need to be removed for (Resource child : children) { // if we've already found all ignored resources, stop looking if (null != uuidsToRemove && uuidsToRemove.size() == uuidsToIgnore.size()) { break; } if (uuidsToIgnore.contains(child.getUuid())) { if (null == childrenToRemove) { childrenToRemove = new HashSet<Resource>(); uuidsToRemove = new HashSet<String>(); } childrenToRemove.add(child); uuidsToRemove.add(child.getUuid()); } } // remove the ignored children and processed uuids if (null != childrenToRemove) { uuidsToIgnore.removeAll(uuidsToRemove); children.removeAll(childrenToRemove); uuidsToRemove.clear(); uuidsToRemove = null; childrenToRemove.clear(); childrenToRemove = null; } // recurse on the remaining children for (Resource child : children) { removeIgnoredResourcesFromChildren(child, uuidsToIgnore); } return; } private void connectTypes(Resource resource, Set<String> uuidsToIgnore) { PluginMetadataManager metadataManager = PluginContainer.getInstance().getPluginManager().getMetadataManager(); ResourceType resourceType = resource.getResourceType(); if (resourceType != null) { ResourceType fullResourceType = metadataManager.getType(resourceType); if (fullResourceType != null) { resource.setResourceType(fullResourceType); // now reconnect all its children's types Set<Resource> children = inventoryManager.getContainerChildren(resource); for (Resource child : children) { connectTypes(child, uuidsToIgnore); } } else { log.info("Persisted resource [" + resource + "] has a disabled resource type - will not reconnect it"); addAllUUIDsToList(resource, uuidsToIgnore); } } else { log.error("Persisted resource [" + resource + "] does not have a resource type - cannot reconnect its type or its children types"); addAllUUIDsToList(resource, uuidsToIgnore); } return; } private void addAllUUIDsToList(Resource resource, Set<String> list) { list.add(resource.getUuid()); Set<Resource> children = inventoryManager.getContainerChildren(resource); for (Resource child : children) { addAllUUIDsToList(child, list); } return; } /** * Given a platform and map of resource containers (keyed on UUID strings), this persists that inventory to the * {@link #getInventoryFile() inventory file}. This object's {@link #getPlatform() platform} and * {@link #getResourceContainers() resource containers} will be set to those passed to this method. * * @param platformResource * @param containers * * @throws IOException */ public void storeInventory(Resource platformResource, Map<String, ResourceContainer> containers) throws IOException { FileOutputStream fos = new FileOutputStream(inventoryFile); try { ObjectOutputStream oos = new ObjectOutputStream(fos); try { oos.writeObject(platformResource); oos.writeObject(containers); this.platform = platformResource; this.resourceContainers = containers; } finally { oos.close(); } } finally { fos.close(); } } }