/* * RHQ Management Platform * Copyright (C) 2005-2012 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ package org.rhq.plugins.hadoop; import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamConstants; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import javax.xml.stream.XMLStreamWriter; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.rhq.core.domain.configuration.Configuration; import org.rhq.core.domain.configuration.Property; import org.rhq.core.domain.configuration.PropertySimple; import org.rhq.core.domain.configuration.definition.ConfigurationDefinition; import org.rhq.core.domain.configuration.definition.PropertyDefinition; import org.rhq.core.domain.configuration.definition.PropertyDefinitionSimple; import org.rhq.core.pluginapi.inventory.ResourceComponent; import org.rhq.core.pluginapi.inventory.ResourceContext; import org.rhq.core.util.file.FileUtil; /** * * * @author Lukas Krejci */ public class HadoopServerConfigurationDelegate { private static final Log LOG = LogFactory.getLog(HadoopServerConfigurationDelegate.class); private static final XMLInputFactory XML_INPUT_FACTORY = XMLInputFactory.newInstance(); private static final XMLOutputFactory XML_OUTPUT_FACTORY = XMLOutputFactory.newInstance(); private static final String CONFIGURATION_TAG_NAME = "configuration"; private static final String PROPERTY_TAG_NAME = "property"; private static final String NAME_TAG_NAME = "name"; private static final String VALUE_TAG_NAME = "value"; private static final Pattern PROPERTY_NAME_EXTRACT_PATTERN = Pattern.compile("<name>(.*)<\\/name>"); private static final Pattern PROPERTY_VALUE_REPLACE_PATTERN = Pattern.compile("<value>.*<\\/value>"); private static class DetectedPropertyNameAndUpdatedTag { String propertyName; String updatedTag; } private static class PropertiesPerConfigFileBuilder { private static class ConfigFileAndConfigName { File configFile; String propertyName; ConfigFileAndConfigName(File homeDir, String propertyDefinitionName) { String[] parts = propertyDefinitionName.split(":"); String configFileName = parts[0]; configFile = new File(homeDir, configFileName); propertyName = parts[1]; if (!configFile.exists()) { throw new IllegalArgumentException("The expected configuration file (" + configFile.getAbsolutePath() + ") doesn't exist."); } } } private Map<File, Map<String, PropertySimple>> propertiesPerFile = new HashMap<File, Map<String, PropertySimple>>(); private File homeDir; public PropertiesPerConfigFileBuilder(File homeDir) { this.homeDir = homeDir; } void addProperty(String name, Configuration parentConfig) { PropertySimple ret = new PropertySimple(); ret.setName(name); parentConfig.put(ret); addProperty(ret); } void addProperty(PropertySimple property) { ConfigFileAndConfigName fn = new ConfigFileAndConfigName(homeDir, property.getName()); Map<String, PropertySimple> props = propertiesPerFile.get(fn.configFile); if (props == null) { props = new HashMap<String, PropertySimple>(); propertiesPerFile.put(fn.configFile, props); } props.put(fn.propertyName, property); } Map<File, Map<String, PropertySimple>> getPropertiesPerFilePerConfigName() { return propertiesPerFile; } } private ResourceContext<ResourceComponent<?>> componentContext; public HadoopServerConfigurationDelegate(ResourceContext<ResourceComponent<?>> componentContext) { this.componentContext = componentContext; } public Configuration loadConfiguration() throws Exception { ConfigurationDefinition definition = componentContext.getResourceType().getResourceConfigurationDefinition(); Configuration config = new Configuration(); File homeDir = getHomeDir(); fillResourceConfiguration(homeDir, config, definition); return config; } public void updateConfiguration(Configuration config) throws Exception { //gather the files to update PropertiesPerConfigFileBuilder bld = new PropertiesPerConfigFileBuilder(getHomeDir()); for (Property p : config.getProperties()) { if (!(p instanceof PropertySimple)) { continue; } PropertySimple property = (PropertySimple) p; bld.addProperty(property); } for (Map.Entry<File, Map<String, PropertySimple>> e : bld.getPropertiesPerFilePerConfigName().entrySet()) { updateFile(e.getKey(), e.getValue()); } } private File getHomeDir() { File homeDir = new File(componentContext.getPluginConfiguration().getSimpleValue(HadoopServerDiscovery.HOME_DIR_PROPERTY)); if (!homeDir.exists()) { throw new IllegalArgumentException("The configured home directory of this Hadoop instance (" + homeDir.getAbsolutePath() + ") no longer exists."); } if (!homeDir.isDirectory()) { throw new IllegalArgumentException("The configured home directory of this Hadoop instance (" + homeDir.getAbsolutePath() + ") is not a directory."); } if (!homeDir.canRead()) { throw new IllegalArgumentException("The configured home directory of this Hadoop instance (" + homeDir.getAbsolutePath() + ") is not readable."); } return homeDir; } public static void fillResourceConfiguration(File homeDir, Configuration config, ConfigurationDefinition definition) throws XMLStreamException, IOException { //the config is just a bunch of simples, so this is rather easy.. no cumbersome traversal of property maps and lists PropertiesPerConfigFileBuilder bld = new PropertiesPerConfigFileBuilder(homeDir); for (PropertyDefinition pd : definition.getPropertyDefinitions().values()) { if (!(pd instanceof PropertyDefinitionSimple)) { //hmm... well, someone thought it's enough to change the config and the code would be clever. //it's not ;) continue; } String propertyName = pd.getName(); bld.addProperty(propertyName, config); } for (Map.Entry<File, Map<String, PropertySimple>> e : bld.getPropertiesPerFilePerConfigName().entrySet()) { File configFile = e.getKey(); Map<String, PropertySimple> propertiesToFind = e.getValue(); parseAndAssignProps(configFile, propertiesToFind); } } public static void parseAndAssignProps(File configFile, Map<String, PropertySimple> props) throws XMLStreamException, IOException { FileInputStream in = new FileInputStream(configFile); XMLStreamReader rdr = XML_INPUT_FACTORY.createXMLStreamReader(in); try { boolean inProperty = false; String propertyName = null; String propertyValue = null; while (rdr.hasNext()) { int event = rdr.next(); String tag = null; switch (event) { case XMLStreamReader.START_ELEMENT: tag = rdr.getName().getLocalPart(); if (PROPERTY_TAG_NAME.equals(tag)) { inProperty = true; } else if (inProperty && NAME_TAG_NAME.equals(tag)) { propertyName = rdr.getElementText(); } else if (inProperty && VALUE_TAG_NAME.equals(tag)) { propertyValue = rdr.getElementText(); } break; case XMLStreamReader.END_ELEMENT: tag = rdr.getName().getLocalPart(); if (PROPERTY_TAG_NAME.equals(tag)) { inProperty = false; PropertySimple prop = props.get(propertyName); if (prop != null) { prop.setValue(propertyValue); } propertyName = null; propertyValue = null; } break; } } } finally { rdr.close(); in.close(); } } private static void updateFile(File configFile, Map<String, PropertySimple> allProps) throws IOException, InterruptedException, XMLStreamException { InputStream in = null; XMLStreamReader rdr = null; OutputStream out = null; XMLStreamWriter outWrt = null; try { Set<String> processedPropertyNames = new HashSet<String>(); in = new BufferedInputStream(new FileInputStream(configFile)); rdr = XML_INPUT_FACTORY.createXMLStreamReader(in); File tmpFile = File.createTempFile("hadoop-plugin", null); out = new FileOutputStream(tmpFile); outWrt = XML_OUTPUT_FACTORY.createXMLStreamWriter(out); ByteArrayOutputStream stash = new ByteArrayOutputStream(); XMLStreamWriter stashWrt = XML_OUTPUT_FACTORY.createXMLStreamWriter(stash); boolean outputActive = true; outWrt.writeStartDocument(); while (rdr.hasNext()) { int event = rdr.next(); XMLStreamWriter wrt = outputActive ? outWrt : stashWrt; switch (event) { case XMLStreamConstants.ATTRIBUTE: break; case XMLStreamConstants.CDATA: wrt.writeCData(rdr.getText()); break; case XMLStreamConstants.CHARACTERS: wrt.writeCharacters(rdr.getText()); break; case XMLStreamConstants.COMMENT: wrt.writeComment(rdr.getText()); break; case XMLStreamConstants.DTD: wrt.writeDTD(rdr.getText()); break; case XMLStreamConstants.END_DOCUMENT: wrt.writeEndDocument(); break; case XMLStreamConstants.END_ELEMENT: if (PROPERTY_TAG_NAME.equals(rdr.getName().getLocalPart())) { String encoding = rdr.getEncoding(); if (encoding == null) { encoding = "UTF-8"; } String propertyTagSoFar = Charset.forName(encoding).decode(ByteBuffer.wrap(stash.toByteArray())).toString(); DetectedPropertyNameAndUpdatedTag propAndTag = updateProperty(propertyTagSoFar, allProps); //yes, we're intentionally circumventing the xml stream writer, because we already have the XML data we want to write. outWrt.flush(); out.write(propAndTag.updatedTag.getBytes("UTF-8")); processedPropertyNames.add(propAndTag.propertyName); //reset stuff stash.reset(); wrt = outWrt; outputActive = true; } else if (CONFIGURATION_TAG_NAME.equals(rdr.getName().getLocalPart())) { //now add the new props for(String prop : processedPropertyNames) { allProps.remove(prop); } for(Map.Entry<String, PropertySimple> e : allProps.entrySet()) { outWrt.writeStartElement(PROPERTY_TAG_NAME); outWrt.writeStartElement(NAME_TAG_NAME); outWrt.writeCharacters(e.getKey()); outWrt.writeEndElement(); outWrt.writeStartElement(VALUE_TAG_NAME); outWrt.writeCharacters(e.getValue().getStringValue()); outWrt.writeEndElement(); outWrt.writeEndElement(); } } wrt.writeEndElement(); break; case XMLStreamConstants.ENTITY_DECLARATION: //XXX could not find what to do with this break; case XMLStreamConstants.ENTITY_REFERENCE: wrt.writeEntityRef(rdr.getText()); break; case XMLStreamConstants.NAMESPACE: for (int i = 0; i < rdr.getNamespaceCount(); ++i) { wrt.writeNamespace(rdr.getNamespacePrefix(i), rdr.getNamespaceURI(i)); } break; case XMLStreamConstants.NOTATION_DECLARATION: //XXX could not find what to do with this break; case XMLStreamConstants.PROCESSING_INSTRUCTION: wrt.writeProcessingInstruction(rdr.getPITarget(), rdr.getPIData()); break; case XMLStreamConstants.SPACE: wrt.writeCharacters(rdr.getText()); break; case XMLStreamConstants.START_DOCUMENT: //this seems to be never called for some strange reason //wrt.writeStartDocument(); break; case XMLStreamConstants.START_ELEMENT: wrt.writeStartElement(rdr.getName().getPrefix(), rdr.getName().getLocalPart(), rdr.getName() .getNamespaceURI()); for (int i = 0; i < rdr.getAttributeCount(); ++i) { wrt.writeAttribute(rdr.getAttributePrefix(i), rdr.getAttributeNamespace(i), rdr.getAttributeLocalName(i), rdr.getAttributeValue(i)); } if (PROPERTY_TAG_NAME.equals(rdr.getName().getLocalPart())) { wrt.writeCharacters(""); outputActive = false; } break; } } outWrt.flush(); out.flush(); out.close(); in.close(); //now copy the temp file in the place of the original one FileUtil.copyFile(tmpFile, configFile); } finally { rdr.close(); outWrt.flush(); outWrt.close(); try { in.close(); } finally { out.flush(); out.close(); } } } private static DetectedPropertyNameAndUpdatedTag updateProperty(String propertyTag, Map<String, PropertySimple> allProps) { DetectedPropertyNameAndUpdatedTag ret = new DetectedPropertyNameAndUpdatedTag(); //for now ret.updatedTag = propertyTag; //extract the name of the property we're dealing with Matcher propertyNameMatcher = PROPERTY_NAME_EXTRACT_PATTERN.matcher(propertyTag); if (!propertyNameMatcher.find()) { return ret; } String propertyName = propertyNameMatcher.group(1); ret.propertyName = propertyName; if (propertyName == null) { if (LOG.isDebugEnabled()) { LOG.debug("Possibly invalid property tag?\n" + propertyTag); } return ret; } //try to find the property in allProps PropertySimple prop = allProps.get(propertyName); if (prop == null) { return ret; } if (prop.getStringValue() == null) { //this property has been unset ret.updatedTag = ""; return ret; } Matcher propertyValueMatcher = PROPERTY_VALUE_REPLACE_PATTERN.matcher(propertyTag); if (!propertyValueMatcher.find()) { return ret; } ret.updatedTag = propertyValueMatcher.replaceAll("<value>" + prop.getStringValue() + "</value>"); return ret; } }