/*
* JBoss, Home of Professional Open Source.
* Copyright 2009, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.ha.core.channelfactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackReader;
import java.io.Reader;
import java.io.StringReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.jboss.logging.Logger;
import org.jboss.util.StringPropertyReplacer;
import org.jgroups.JChannel;
import org.jgroups.conf.ConfiguratorFactory;
import org.jgroups.util.Util;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* Utilities related to JGroups protocol stack manipulation. This duplicates
* a lot of things in JGroups, but does so in order to eliminate usage of
* classes that JGroups considers to be internal JGroups implementation details
* that are subject to change at any time.
*
* @author Brian Stansberry
*
* @version $Revision: $
*/
public final class ProtocolStackUtil
{
private static final Logger log = Logger.getLogger(ProtocolStackUtil.class);
private static final String PROTOCOL_STACKS="protocol_stacks";
private static final String STACK="stack";
private static final String NAME="name";
private static final String DESCR="description";
private static final String CONFIG="config";
/**
* Parses the contents of <code>input</code> into a map of the
* protocol stack configurations contained in the XML.
*
* @param input stream which must contain XML content in the JGroups
* <code>stacks.xml</code> format
*
* @return a map of the protocol stack configurations contained in the XML
*
* @throws IllegalArgumentException if <code>input</code> is <code>null</code>
* @throws Exception
*/
public static Map<String, ProtocolStackConfigInfo> parse(InputStream input) throws Exception
{
if (input == null)
{
throw new IllegalArgumentException("null input");
}
DocumentBuilderFactory factory=DocumentBuilderFactory.newInstance();
factory.setValidating(false); //for now
DocumentBuilder builder=factory.newDocumentBuilder();
Document document=builder.parse(input);
// The root element of the document should be the "protocol_stacks" element,
// but the parser(Element) method checks this so a check is not
// needed here.
Element configElement = document.getDocumentElement();
return parse(configElement);
}
/**
* Parses the contents of <code>root</code> into a map of the
* protocol stack configurations contained in the XML.
*
* @param root document root node for XML content in the JGroups
* <code>stacks.xml</code> format
*
* @return a map of the protocol stack configurations contained in the XML
*
* @throws IllegalArgumentException if <code>input</code> is <code>null</code>
* @throws Exception
*/
public static Map<String, ProtocolStackConfigInfo> parse(Element root) throws Exception
{
if (root == null)
{
throw new IllegalArgumentException("null root");
}
String root_name = root.getNodeName();
if (!PROTOCOL_STACKS.equals(root_name.trim().toLowerCase()))
{
throw new IOException("Invalid XML configuration: configuration does not start with a '" +
PROTOCOL_STACKS + "' element");
}
Map<String, ProtocolStackConfigInfo> result = new HashMap<String, ProtocolStackConfigInfo>();
NodeList tmp_stacks = root.getChildNodes();
for (int i = 0; i < tmp_stacks.getLength(); i++)
{
Node node = tmp_stacks.item(i);
if (node.getNodeType() != Node.ELEMENT_NODE)
continue;
Element stack = (Element) node;
String tmp = stack.getNodeName();
if (!STACK.equals(tmp.trim().toLowerCase()))
{
throw new IOException("Invalid configuration: didn't find a \"" + STACK + "\" element under \""
+ PROTOCOL_STACKS + "\"");
}
NamedNodeMap attrs = stack.getAttributes();
Node name = attrs.getNamedItem(NAME);
String st_name = name.getNodeValue();
Node descr=attrs.getNamedItem(DESCR);
String stack_descr=descr.getNodeValue();
if (log.isTraceEnabled())
{
log.trace("Parsing \"" + st_name + "\" (" + stack_descr + ")");
}
NodeList configs = stack.getChildNodes();
for (int j = 0; j < configs.getLength(); j++)
{
Node tmp_config = configs.item(j);
if (tmp_config.getNodeType() != Node.ELEMENT_NODE)
continue;
Element cfg = (Element) tmp_config;
tmp = cfg.getNodeName();
if (!CONFIG.equals(tmp))
{
throw new IOException("Invalid configuration: didn't find a \"" +
CONFIG + "\" element under \"" + STACK + "\"");
}
ProtocolData[] protocolData = parseConfig(cfg);
// replace vars with system props
substituteVariables(protocolData);
result.put(st_name, new ProtocolStackConfigInfo(st_name, stack_descr, protocolData));
}
}
return result;
}
public static ProtocolData[] parseSingleConfig(InputStream input) throws Exception
{
if (input == null)
{
throw new IllegalArgumentException("null input");
}
DocumentBuilderFactory factory=DocumentBuilderFactory.newInstance();
factory.setValidating(false); //for now
DocumentBuilder builder=factory.newDocumentBuilder();
Document document=builder.parse(input);
// The root element of the document should be the "config" element,
// but the parser(Element) method checks this so a check is not
// needed here.
Element configElement = document.getDocumentElement();
return parseConfig(configElement);
}
public static ProtocolData[] parseConfig(Element root_element) throws java.io.IOException
{
/** LinkedList<ProtocolData> */
List<ProtocolData> prot_data = new LinkedList<ProtocolData>();
/**
* CAUTION: crappy code ahead ! I (bela) am not an XML expert, so the
* code below is pretty amateurish... But it seems to work, and it is
* executed only on startup, so no perf loss on the critical path. If
* somebody wants to improve this, please be my guest.
*/
try
{
String root_name = root_element.getNodeName();
if (!"config".equals(root_name.trim().toLowerCase()))
{
log.fatal("XML protocol stack configuration does not start with a '<config>' element; "
+ "maybe the XML configuration needs to be converted to the new format ?\n"
+ "use 'java org.jgroups.conf.XmlConfigurator <old XML file> -new_format' to do so");
throw new IOException("invalid XML configuration");
}
NodeList prots = root_element.getChildNodes();
for (int i = 0; i < prots.getLength(); i++)
{
Node node = prots.item(i);
if (node.getNodeType() != Node.ELEMENT_NODE)
continue;
Element tag = (Element) node;
String protocol = tag.getTagName();
// System.out.println("protocol: " + protocol);
LinkedList<ProtocolParameter> params = new LinkedList<ProtocolParameter>();
NamedNodeMap attrs = tag.getAttributes();
int attrLength = attrs.getLength();
for (int a = 0; a < attrLength; a++)
{
Attr attr = (Attr) attrs.item(a);
String name = attr.getName();
String value = attr.getValue();
// System.out.println(" name=" + name + ", value=" + value);
params.add(new ProtocolParameter(name, value));
}
ProtocolData data = new ProtocolData(protocol, protocol, params);
prot_data.add(data);
}
return prot_data.toArray(new ProtocolData[prot_data.size()]);
}
catch (Exception x)
{
if (x instanceof java.io.IOException)
throw (java.io.IOException) x;
else
{
throw new IOException(x);
}
}
}
public static void substituteVariables(ProtocolData[] protocols)
{
for (ProtocolData data : protocols)
{
if (data != null)
{
for (ProtocolParameter param : data.getParametersAsArray())
{
String val = param.getValue();
// We use jboss-common-core here so we get the richer feature set
// of that versus JGroups system property replacement
String replacement = StringPropertyReplacer.replaceProperties(val);
if (!replacement.equals(val))
{
param.setValue(replacement);
}
}
}
}
}
public static String getProtocolStackString(ProtocolData[] protocolData)
{
StringBuilder buf = new StringBuilder();
for (int i = 0; i < protocolData.length; i++)
{
ProtocolData pd = protocolData[i];
buf.append(pd.getProtocolString());
if (i < protocolData.length - 1)
{
buf.append(':');
}
}
return buf.toString();
}
private static List<String> parseProtocols(String config_str) throws IOException
{
List<String> retval = new LinkedList<String>();
PushbackReader reader = new PushbackReader(new StringReader(config_str));
int ch;
StringBuilder sb;
boolean running = true;
while (running)
{
String protocol_name = readWord(reader);
sb = new StringBuilder();
sb.append(protocol_name);
ch = read(reader);
if (ch == -1)
{
retval.add(sb.toString());
break;
}
if (ch == ':')
{ // no attrs defined
retval.add(sb.toString());
continue;
}
if (ch == '(')
{ // more attrs defined
reader.unread(ch);
String attrs = readUntil(reader, ')');
sb.append(attrs);
retval.add(sb.toString());
}
else
{
retval.add(sb.toString());
}
while (true)
{
ch = read(reader);
if (ch == ':')
{
break;
}
if (ch == -1)
{
running = false;
break;
}
}
}
reader.close();
return retval;
}
private static int read(Reader reader) throws IOException
{
int ch = -1;
while ((ch = reader.read()) != -1)
{
if (!Character.isWhitespace(ch))
return ch;
}
return ch;
}
private static String readUntil(Reader reader, char c) throws IOException
{
StringBuilder sb = new StringBuilder();
int ch;
while ((ch = read(reader)) != -1)
{
sb.append((char) ch);
if (ch == c)
break;
}
return sb.toString();
}
private static String readWord(PushbackReader reader) throws IOException
{
StringBuilder sb = new StringBuilder();
int ch;
while ((ch = read(reader)) != -1)
{
if (Character.isLetterOrDigit(ch) || ch == '_' || ch == '.' || ch == '$')
{
sb.append((char) ch);
}
else
{
reader.unread(ch);
break;
}
}
return sb.toString();
}
private static ProtocolData parseProtocol(String protocolConfig) throws Exception
{
String protocol_name = null;
String properties_str = null;
int index = protocolConfig.indexOf('('); // e.g. "UDP(in_port=3333)"
int end_index = protocolConfig.lastIndexOf(')');
if (index == -1)
{
protocol_name = protocolConfig;
properties_str = "";
}
else
{
if (end_index == -1)
{
throw new Exception("Configurator.ProtocolConfiguration(): closing ')' " + "not found in " + protocolConfig
+ ": properties cannot be set !");
}
else
{
properties_str = protocolConfig.substring(index + 1, end_index);
protocol_name = protocolConfig.substring(0, index);
}
}
List<ProtocolParameter> params = parsePropertiesString(protocol_name, properties_str);
return new ProtocolData(protocol_name, protocol_name, params);
}
private static List<ProtocolParameter> parsePropertiesString(String protocol_name, String properties_str)
throws Exception
{
List<ProtocolParameter> params = new ArrayList<ProtocolParameter>();
int index = 0;
/* "in_port=5555;out_port=6666" */
if (properties_str.length() > 0)
{
String[] components = properties_str.split(";");
for (String property : components)
{
String name, value;
index = property.indexOf('=');
if (index == -1)
{
throw new Exception("'=' not found in " + property + " of " + protocol_name);
}
name = property.substring(0, index);
value = property.substring(index + 1, property.length());
params.add(new ProtocolParameter(name, value));
}
}
return params;
}
public static ProtocolData[] getProtocolData(Object properties) throws Exception {
InputStream input=null;
// added by bela: for null String props we use the default properties
if(properties == null)
properties=JChannel.DEFAULT_PROTOCOL_STACK;
if(properties instanceof URL) {
try {
input=((URL)properties).openStream();
}
catch(Throwable t) {
}
}
// if it is a string, then it could be a plain string or a url
if(input == null && properties instanceof String) {
try {
input=new URL((String)properties).openStream();
}
catch(Exception ignore) {
// if we get here this means we don't have a URL
}
// another try - maybe it is a resource, e.g. udp.xml
if(input == null && ((String)properties).endsWith("xml")) {
try {
input=Util.getResourceAsStream((String)properties, ConfiguratorFactory.class);
}
catch(Throwable ignore) {
}
}
// try a regular file name
//
// This code was moved from the parent block (below) because of the
// possibility of causing a ClassCastException.
if(input == null) {
try {
input=new FileInputStream((String)properties);
}
catch(Throwable t) {
}
}
}
// try a regular file
if(input == null && properties instanceof File) {
try {
input=new FileInputStream((File)properties);
}
catch(Throwable t) {
}
}
if(input != null) {
return parseSingleConfig(input);
}
if(properties instanceof Element) {
return parseConfig((Element)properties);
}
return parseProtocolStackString((String)properties);
}
private static ProtocolData[] parseProtocolStackString(String configuration) throws Exception
{
List<ProtocolData> retval = new ArrayList<ProtocolData>();
List<String> protocol_string = parseProtocols(configuration);
if (protocol_string == null)
return null;
for (String component_string : protocol_string)
{
retval.add(parseProtocol(component_string));
}
return retval.toArray(new ProtocolData[retval.size()]);
}
/**
* Prevent instantiation.
*/
private ProtocolStackUtil()
{
}
}