/* * Jopr Management Platform * Copyright (C) 2005-2008 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, version 2, as * published by the Free Software Foundation, and/or the GNU Lesser * General Public License, version 2.1, also as published by the Free * Software Foundation. * * 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 and the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU General Public License * and the GNU Lesser 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.plugins.jbossas.util; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.util.HashMap; import java.util.Properties; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jetbrains.annotations.Nullable; import org.rhq.core.pluginapi.util.SelectiveSkippingEntityResolver; import org.xml.sax.Attributes; import org.xml.sax.EntityResolver; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; import org.xml.sax.helpers.DefaultHandler; import org.rhq.core.util.StringPropertyReplacer; import org.rhq.plugins.jbossas.helper.JBossProperties; /** * This class will parse the passed File in the getConfig method. This file is normally * the jboss-service.xml file in the conf directory of the JBoss Server. * * Using a SaxParser it will look for the MBean tag for Naming, when it finds that it is * in that tag, it will then look for the Port attribute to determine the jnp port * * The JBossServerHandler class handles the searching, by determining when the SAX parser * has started reading the element that has the text that is being searched, and when that element * has ended. * * Config parsing intended only for JBoss server auto discovery. */ public class JnpConfig { private static Log log = LogFactory.getLog(JnpConfig.class); static final String PROPERTY_EXPRESSION_PREFIX = "${"; private static final HashMap<File, JnpConfig> CACHE = new HashMap<File, JnpConfig>(); private String jnpAddress; private Integer jnpPort; private long lastModified = 0; private String serverName; private File storeFile; private Properties systemProperties; private JnpConfig(Properties systemProperties) { this.systemProperties = systemProperties; } public static synchronized JnpConfig getConfig(File configXML, Properties systemProperties) { JnpConfig config = CACHE.get(configXML); long lastModified = configXML.lastModified(); if ((config == null) || (lastModified != config.lastModified)) { config = new JnpConfig(systemProperties); config.lastModified = lastModified; CACHE.put(configXML, config); try { config.read(configXML); } catch (IOException e) { e.printStackTrace(); } } return config; } /** * Returns the JNP port, or null if the port could not be determined. * * @return the JNP port, or null if the port could not be determined */ @Nullable public Integer getJnpPort() { return this.jnpPort; } /** * Returns the JNP address, or null if the address could not be determined. * * @return the JNP address, or null if the address could not be determined */ @Nullable public String getJnpAddress() { return this.jnpAddress; } private void read(File file) throws IOException { try { parseServiceXML(file); } catch (SAXException e) { throw new IllegalArgumentException(e.getMessage()); } catch (ParserConfigurationException e) { throw new IllegalArgumentException(e.getMessage()); } // Not in that first xml - let's try the binding service xml... if (this.jnpPort == null && this.storeFile != null) { parseBindingManagerXML(); } } private void parseServiceXML(File serviceXmlFile) throws IOException, SAXException, ParserConfigurationException { FileInputStream is = null; try { is = new FileInputStream(serviceXmlFile); SAXParserFactory factory = SAXParserFactory.newInstance(); SAXParser parser = factory.newSAXParser(); XMLReader reader = parser.getXMLReader(); EntityResolver entityResolver = SelectiveSkippingEntityResolver.getDtdAndXsdSkippingInstance(); reader.setEntityResolver(entityResolver); JBossServiceHandler contentHandler = new JBossServiceHandler(serviceXmlFile); reader.setContentHandler(contentHandler); reader.parse(new InputSource(is)); this.jnpAddress = contentHandler.getNamingBindAddress(); this.jnpPort = contentHandler.getNamingPort(); this.storeFile = contentHandler.getStoreFile(); this.serverName = contentHandler.getServerName(); } finally { if (is != null) { is.close(); } } } private void parseBindingManagerXML() throws IOException { InputStream bindIs = null; try { SAXParserFactory factory = SAXParserFactory.newInstance(); SAXParser parser = factory.newSAXParser(); if (!this.storeFile.isFile()) { log.warn("Store file does not exist: " + this.storeFile); return; } JBossBindingManagerHandler bindingHandler = new JBossBindingManagerHandler(this.storeFile); bindingHandler.setServerName(this.serverName); XMLReader reader = parser.getXMLReader(); bindIs = new FileInputStream(this.storeFile); reader.setContentHandler(bindingHandler); reader.parse(new InputSource(bindIs)); this.jnpAddress = bindingHandler.getJnpAddress(); this.jnpPort = bindingHandler.getJnpPort(); } catch (Exception e) { throw new IllegalArgumentException(e.getMessage()); } finally { if (bindIs != null) { bindIs.close(); } } } private class JBossBindingManagerHandler extends DefaultHandler { private File file; private boolean inServer = false; private boolean inServiceConfig = false; private boolean inNamingPort = false; private String jnpAddress; private Integer jnpPort; private String serverName; private JBossBindingManagerHandler(File file) { this.file = file; } @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { if (qName.equals("server")) { String name = attributes.getValue("name"); if ((name != null) && name.equals(serverName)) { inServer = true; return; } } if (inServer && qName.equals("service-config")) { String name = attributes.getValue("name"); if ((name != null) && name.equals("jboss:service=Naming")) { inServiceConfig = true; return; } } if (inServiceConfig && qName.equals("binding")) { jnpAddress = replaceProperties(attributes.getValue("host")); if (jnpAddress.substring(0, PROPERTY_EXPRESSION_PREFIX.length()).equals(PROPERTY_EXPRESSION_PREFIX)) { log.warn("Naming binding 'host' attribute has invalid value (" + jnpAddress + ") in JBossAS config file " + file + " - the value should be a host name, an IP address, or a resolvable property reference."); jnpAddress = null; } String jnpPortString = replaceProperties(attributes.getValue("port")); try { jnpPort = Integer.parseInt(jnpPortString); } catch (NumberFormatException e) { log.warn("Naming binding 'port' attribute has invalid value (" + jnpPortString + ") in JBossAS config file " + file + " - the value should be a positive integer or a resolvable property reference."); jnpPort = null; } } } @Override public void endElement(String uri, String localName, String qName) { if (inServiceConfig && qName.equals("binding")) { inServiceConfig = false; } if (inNamingPort && qName.equals("service-config")) { inNamingPort = false; } if (inServer && qName.equals("server")) { inServer = false; } } public String getJnpAddress() { return this.jnpAddress; } protected Integer getJnpPort() { return this.jnpPort; } protected void setServerName(String serverName) { this.serverName = serverName; } } private class JBossServiceHandler extends DefaultHandler { private static final String DEFAULT_JNP_ADDRESS = "0.0.0.0"; private static final String DEFAULT_JNP_PORT = "1099"; private File file; //Naming Service private boolean inNaming = false; private boolean inNamingPort = false; private boolean inNamingBindAddress = false; private StringBuilder namingPort = null; private StringBuilder namingBindAddress = null; //Binding Manager private boolean inBinding = false; private boolean inServerName = false; private boolean inStoreURL = false; private boolean isBindingManagerInUse = false; private StringBuilder storeURL = new StringBuilder(); private StringBuilder serverName = new StringBuilder(); private JBossServiceHandler(File filePath) { this.file = filePath; } @Override public void characters(char[] ch, int start, int length) throws SAXException { if (inNamingBindAddress) { namingBindAddress.append(ch, start, length); } else if (inNamingPort) { namingPort.append(ch, start, length); } else if (inServerName) { serverName.append(ch, start, length); } else if (inStoreURL) { storeURL.append(ch, start, length); } } @Override public void endElement(String uri, String localName, String qName) { if (inNamingPort && qName.equals("attribute")) { inNamingPort = false; namingPort = processValue(namingPort, DEFAULT_JNP_PORT); return; } if (inNamingBindAddress && qName.equals("attribute")) { inNamingBindAddress = false; namingBindAddress = processValue(namingBindAddress, DEFAULT_JNP_ADDRESS); return; } if (inServerName && qName.equals("attribute")) { inServerName = false; serverName = processValue(serverName, null); return; } if (inStoreURL && qName.equals("attribute")) { storeURL = processValue(storeURL, null); inStoreURL = false; return; } if (inNaming && qName.equals("mbean")) { inNaming = false; return; } if (inBinding && qName.equals("mbean")) { inBinding = false; } } @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { if (inNaming && qName.equals("attribute")) { String name = attributes.getValue("name"); if (name != null) { if (name.equals("Port")) { inNamingPort = true; namingPort = new StringBuilder(); return; } else if (name.equals("BindAddress")) { inNamingBindAddress = true; namingBindAddress = new StringBuilder(); return; } } } if (inBinding && qName.equals("attribute")) { String name = attributes.getValue("name"); if ((name != null) && name.equals("ServerName")) { inServerName = true; return; } if ((name != null) && name.equals("StoreURL")) { inStoreURL = true; return; } } if (qName.equals("mbean")) { String name = attributes.getValue("name"); if ((name != null) && name.equals("jboss:service=Naming")) { inNaming = true; return; } if ((name != null) && name.equals("jboss.system:service=ServiceBindingManager")) { inBinding = true; isBindingManagerInUse = true; } } } protected Integer getNamingPort() { if (isBindingManagerInUse) { return null; } if (null == namingPort) { log.warn("Naming 'RmiPort' attribute not found in JBossAS config file " + file + ". This may be ok as it can be specified in more than one place."); return null; } try { return Integer.parseInt(namingPort.toString()); } catch (NumberFormatException e) { log.warn("Naming 'Port' attribute has invalid value (" + namingPort + ") in JBossAS config file " + file + " - the value should be a positive integer or a resolvable property reference."); return null; } } protected String getNamingBindAddress() { if (isBindingManagerInUse) { return null; } if (null == namingBindAddress) { log.warn("Naming 'BindingAddress' attribute not found in JBossAS config file " + file + ". This may be ok as it can be specified in more than one place."); return null; } String bindAddressString = namingBindAddress.toString(); if ((null == bindAddressString) || bindAddressString.startsWith(PROPERTY_EXPRESSION_PREFIX)) { log.warn("Naming 'BindingAddress' attribute has invalid value (" + namingBindAddress + ") in JBossAS config file " + file + ". The value should be a host name, an IP address, or a resolvable property reference."); return null; } return bindAddressString; } protected String getServerName() { return serverName.toString(); } protected File getStoreFile() { File homeDir = new File(systemProperties.getProperty(JBossProperties.HOME_DIR)); try { URL url = JBossConfigurationUtility.makeURL(storeURL.toString(), homeDir); if (!"file".equals(url.getProtocol())) { // TODO: Do we need to support non-file URL's too? throw new MalformedURLException(); } return new File(url.getPath()); } catch (MalformedURLException e) { log.warn("Binding 'StoreURL' attribute has invalid value (" + storeURL + ") in JBossAS config file " + file + " - the value should be a file URL or file path."); return null; } } private StringBuilder processValue(StringBuilder value, String defaultValue) { String stringValue = value.toString().trim(); if (stringValue.equals("") && defaultValue != null) { stringValue = defaultValue; } stringValue = replaceProperties(stringValue); return new StringBuilder(stringValue); } } private String replaceProperties(String value) { return (value != null) ? StringPropertyReplacer.replaceProperties(value, this.systemProperties) : null; } public static void main(String[] args) throws Exception { if (args.length != 1) { System.err.println("Usage: <config file>"); System.exit(1); } File serviceFile = new File(args[0]); File distDir = serviceFile.getParentFile().getParentFile().getParentFile().getParentFile(); // Pass in an empty set of System properties. JnpConfig cfg = JnpConfig.getConfig(serviceFile, new Properties()); System.out.println("JNP address: " + cfg.getJnpAddress()); System.out.println("JNP port: " + cfg.getJnpPort()); } }