Parsing Tomcat's server.xml using Ruby

I needed a tool to create a flat file database from Tomcat's server.xml that I could then later reference when migrating sites, when creating a snapshot, etc. This is what I came up with:

#!/usr/bin/ruby

require 'rubygems'
require 'optparse'
require 'nokogiri'

options = {}
optparse = OptionParser.new do |opts|
  opts.banner = "Usage: #{File.basename($0)} [options]"

  opts.on("-d", "--debug",
    "Enables some helpful debugging output."
  ) { |value| options[:debug] = true }

  opts.on("-f", "--file FILE",
    "The server.xml file you would like to load."
  ) { |value| options[:file] = value }

  opts.on("-o", "--old",
    "Generate using the old formatting."
  ) { |value| options[:old] = value }

  opts.on("-h", "--help", "Display this help message.") do
    puts opts
    exit
  end
end

begin
  optparse.parse!
rescue OptionParser::MissingArgument
  puts "You're missing an argument...\n\n"
  puts optparse
  exit
end

p options if options[:debug] == true
p ARGV    if options[:debug] == true

## MAIN PROGRAM: ##
###################
file_to_parse = options[:file] ? options[:file] : "/opt/tomcat/conf/server.xml"

if ! FileTest.exist? file_to_parse
  $stderr.puts "#{file_to_parse} doesn't exist."
  $stderr.puts "   maybe you should use the -f option?\n\n"
  $stderr.puts optparse
  exit
elsif ! FileTest.readable? file_to_parse
  $stderr.puts "#{file_to_parse} isn't readable, exiting..."
  exit
end
puts "Parsing: " + file_to_parse if options[:debug] == true

## Create a table so we can lookup the line number for the </Service> for the particular service we want
lines = File.open(file_to_parse).readlines.collect { |x| x.strip }
lines.unshift('placing item in array so the index reflects the actual line number')

services_start = (0 .. lines.count - 1).find_all { |x| lines[x,1].to_s.match(/<Service/) }
services_end   = (0 .. lines.count - 1).find_all { |x| lines[x,1].to_s.match(/<\/Service/) }
service_line_numbers = services_start.zip(services_end)
p service_line_numbers if options[:debug] == true

end_line_lookup = service_line_numbers.inject({}) do |result, element|
  key   = element.first
  value = element.last
  result[key] = value
  result
end

xml_doc = Nokogiri::XML(File.open(file_to_parse))

xml_doc.xpath('//Service').each do |service|
  name = service[:name]
  start_line = service.line
  end_line   = end_line_lookup[start_line]

  # Create an array with all connectors and the needed attributes in a hash
  connectors = []
  service.children.css('Connector').each do |connector|
    connectors << {:ip => connector[:address], :port => connector[:port]}
  end

  # Create an array with all docBases
  docBase = []
  service.children.css('Context').each do |context|
    docBase << context[:docBase]
  end

  resources = []
  service.children.css('Resource').each do |resource|
    db_host     = resource[:url].scan(%r{mysql://(.*)/}).join.sub(/^127\.0\.0\.1$/, "localhost")
    schema_name = resource[:url].scan(%r{/([^/]+)\?}).join

    if resources.empty?
      resources << {:db_host => db_host, :schema_name => schema_name}
    else
      duplicate=""
      resources.each do |i|
        if i[:db_host] == db_host and i[:schema_name] == schema_name
          duplicate=true
        end
      end
      if duplicate != true
        resources << {:db_host => db_host, :schema_name => schema_name}
      end
    end
  end

  if options[:old] == true
    # Create schemas array for all schema names, then add NAMEuser schema.
    schemas = resources.inject([]) do |result, resource|
      result << resource[:schema_name]
      result
    end

    # Create dbhost variable and check some stuff out
    db_hosts = resources.inject([]) do |result, resource|
      result << resource[:db_host]
      result
    end.uniq
    if db_hosts.count > 1
      puts "WARNING: We found miltiple hosts for the schema's, exiting..."
      p db_hosts if options[:debug] == true
      exit 1
    end

    schemas << "#{schemas[0]}user"
    # Create ips array for all ip addresses
    ips = connectors.inject([]) do |result, connector|
      result << connector[:ip]
      result
    end
    puts "#{start_line},#{end_line}:#{ips.uniq.join(',')}:#{docBase.uniq.join(',')}:#{db_hosts.join(',')}:#{schemas.join(',')}:#{name}"
  else
    serviceDefintion = {:start_line => start_line, :end_line => end_line, :name => name, :connectors => connectors.uniq, :docBases => docBase.uniq, :resources => resources}
    p serviceDefintion
  end
end

Loading Comments...