
org.apache.activemq.apollo.broker.Broker.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of apollo-broker Show documentation
Show all versions of apollo-broker Show documentation
A reliable messaging server.
The newest version!
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.apollo.broker
import _root_.java.io.File
import _root_.java.lang.String
import org.fusesource.hawtdispatch._
import org.fusesource.hawtbuf._
import collection.JavaConversions
import JavaConversions._
import security._
import org.apache.activemq.apollo.broker.web._
import collection.mutable.{HashSet, LinkedHashMap, HashMap}
import org.apache.activemq.apollo.util._
import org.fusesource.hawtbuf.AsciiBuffer._
import CollectionsSupport._
import FileSupport._
import management.ManagementFactory
import org.apache.activemq.apollo.dto._
import javax.management.ObjectName
import org.fusesource.hawtdispatch.TaskTracker._
import java.util.concurrent.TimeUnit._
import reflect.BeanProperty
import java.net.InetSocketAddress
import org.fusesource.hawtdispatch.util.BufferPools
import org.apache.activemq.apollo.filter.{Filterable, XPathExpression, XalanXPathEvaluator}
import org.xml.sax.InputSource
import java.util
import javax.management.openmbean.CompositeData
import javax.net.ssl.SSLContext
import util.Properties
import language.implicitConversions
/**
*
* The BrokerFactory creates Broker objects from a URI.
*
*
* @author Hiram Chirino
*/
trait BrokerFactoryTrait {
def createBroker(brokerURI:String, props:util.Properties): Broker
}
/**
*
* The BrokerFactory creates Broker objects from a URI.
*
*
* @author Hiram Chirino
*/
object BrokerFactory {
val finder = new ClassFinder[BrokerFactoryTrait]("META-INF/services/org.apache.activemq.apollo/broker-factory.index",classOf[BrokerFactoryTrait])
def createBroker(brokerURI:String):Broker = {
val props = new Properties
for( entry <- System.getenv().entrySet() ) {
props.put("env."+entry.getKey, entry.getValue)
}
props.putAll(System.getProperties)
createBroker(brokerURI, props)
}
def createBroker(uri:String, props:util.Properties):Broker = {
if( uri == null ) {
return null
}
finder.singletons.foreach { provider=>
val broker = provider.createBroker(uri, props)
if( broker!=null ) {
return broker;
}
}
throw new IllegalArgumentException("Uknonwn broker uri: "+uri)
}
}
object BufferConversions {
implicit def toAsciiBuffer(value:String) = new AsciiBuffer(value)
implicit def toUTF8Buffer(value:String) = new UTF8Buffer(value)
implicit def fromAsciiBuffer(value:AsciiBuffer) = value.toString
implicit def fromUTF8Buffer(value:UTF8Buffer) = value.toString
implicit def toAsciiBuffer(value:Buffer) = value.ascii
implicit def toUTF8Buffer(value:Buffer) = value.utf8
}
/**
*
*
*
* @author Hiram Chirino
*/
object BrokerRegistry extends Log {
private val brokers = HashSet[Broker]()
@volatile
private var monitor_session = 0
def list():Array[Broker] = this.synchronized {
brokers.toArray
}
def add(broker:Broker) = this.synchronized {
val rc = brokers.add(broker)
if(rc && brokers.size==1 && java.lang.Boolean.getBoolean("hawtdispatch.profile")) {
// start monitoring when the first broker starts..
monitor_session += 1
monitor_hawtdispatch(monitor_session)
}
rc
}
def remove(broker:Broker) = this.synchronized {
val rc = brokers.remove(broker)
if(rc && brokers.size==0 && java.lang.Boolean.getBoolean("hawtdispatch.profile")) {
// stomp monitoring when the last broker stops..
monitor_session += 1
}
rc
}
def monitor_hawtdispatch(session_id:Int):Unit = {
import collection.JavaConversions._
import java.util.concurrent.TimeUnit._
getGlobalQueue().after(1, SECONDS) {
if( session_id == monitor_session ) {
val m = Dispatch.metrics.toList.flatMap{x=>
if( x.totalWaitTimeNS > MILLISECONDS.toNanos(10) || x.totalRunTimeNS > MILLISECONDS.toNanos(10) ) {
Some(x)
} else {
None
}
}
if( !m.isEmpty ) {
info("-- hawtdispatch metrics -----------------------\n"+m.mkString("\n"))
}
monitor_hawtdispatch(session_id)
}
}
}
}
object Broker extends Log {
val mbean_server = ManagementFactory.getPlatformMBeanServer()
val MAX_JVM_HEAP_SIZE = try {
val data = mbean_server.getAttribute(new ObjectName("java.lang:type=Memory"), "HeapMemoryUsage").asInstanceOf[CompositeData]
data.get("max").asInstanceOf[java.lang.Long].longValue()
} catch {
case _:Throwable => 1024L * 1024 * 1024 // assume it's 1 GIG (that's the default apollo ships with)
}
val BLOCKABLE_THREAD_POOL = ApolloThreadPool.INSTANCE
private val SERVICE_TIMEOUT = 1000*5;
val buffer_pools = new BufferPools
// Make sure XPATH selector support is enabled and optimize a little.
XPathExpression.XPATH_EVALUATOR_FACTORY = new XPathExpression.XPathEvaluatorFactory {
def create(xpath: String): XPathExpression.XPathEvaluator = {
new XalanXPathEvaluator(xpath) {
override def evaluate(m: Filterable): Boolean = {
val body: Buffer = m.getBodyAs(classOf[Buffer])
if (body != null) {
evaluate(new InputSource(new BufferInputStream(body)))
} else {
super.evaluate(m)
}
}
}
}
}
def class_loader:ClassLoader = ClassFinder.class_loader
@volatile
var now = System.currentTimeMillis()
val version = using(getClass().getResourceAsStream("version.txt")) { source=>
read_text(source).trim
}
def capture(command:String*) = {
import ProcessSupport._
try {
system(command:_*) match {
case(0, out, _) => Some(new String(out).trim)
case _ => None
}
} catch {
case _:Throwable => None
}
}
val os = {
val os = System.getProperty("os.name")
val rc = os +" "+System.getProperty("os.version")
// Try to get a better version from the OS itself..
val los = os.toLowerCase()
if( los.startsWith("linux") ) {
capture("lsb_release", "-sd").map("%s (%s)".format(rc, _)).getOrElse(rc)
} else {
rc
}
}
val jvm = {
val vendor = System.getProperty("java.vendor")
val version =System.getProperty("java.version")
val vm =System.getProperty("java.vm.name")
"%s %s (%s)".format(vm, version, vendor)
}
val max_fd_limit = {
if( System.getProperty("os.name").toLowerCase().startsWith("windows") ) {
None
} else {
mbean_server.getAttribute(new ObjectName("java.lang:type=OperatingSystem"), "MaxFileDescriptorCount") match {
case x:java.lang.Long=> Some(x.longValue)
case _ => None
}
}
}
}
/**
*
* A Broker is parent object of all services assoicated with the serverside of
* a message passing system. It keeps track of all running connections,
* virtual hosts and assoicated messaging destintations.
*
*
* @author Hiram Chirino
*/
class Broker() extends BaseService with SecuredResource with PluginStateSupport {
import Broker._
@BeanProperty
var tmp: File = _
@BeanProperty
var config: BrokerDTO = new BrokerDTO
/**
* A reference to a container that created the broker.
* This might something like an ServletContext or an OSGi service factory.
*/
var container:AnyRef = _
config.virtual_hosts.add({
val rc = new VirtualHostDTO
rc.id = "default"
rc.host_names.add("localhost")
rc
})
config.connectors.add({
val rc = new AcceptingConnectorDTO()
rc.id = "default"
rc.bind = "tcp://0.0.0.0:0"
rc
})
@volatile
var default_virtual_host: VirtualHost = null
val virtual_hosts = LinkedHashMap[AsciiBuffer, VirtualHost]()
val virtual_hosts_by_hostname = new LinkedHashMap[AsciiBuffer, VirtualHost]()
/**
* This is a copy of the virtual_hosts_by_hostname variable which
* can be accessed by any thread.
*/
@volatile
var cow_virtual_hosts_by_hostname = Map[AsciiBuffer, VirtualHost]()
val connectors = LinkedHashMap[String, Connector]()
val connections = LinkedHashMap[Long, BrokerConnection]()
// Each period is 1 second long..
object PeriodStat {
def apply(values:Seq[PeriodStat]) = {
val rc = new PeriodStat
for( s <- values ) {
rc.max_connections = rc.max_connections.max(s.max_connections)
}
rc
}
}
class PeriodStat {
// yeah just tracking max connections for now.. but we might add more later.
var max_connections = 0
}
var current_period:PeriodStat = new PeriodStat
val stats_of_5min = new CircularBuffer[PeriodStat](5*60) // collects 5 min stats.
var max_connections_in_5min = 0
var auto_tuned_send_receiver_buffer_size = 64*1024
val dispatch_queue = createQueue("broker")
var id = "default"
val connection_id_counter = new LongCounter
var key_storage:KeyStorage = _
var web_server:WebServer = _
@volatile
def now = Broker.now
var config_log:Log = Log(new MemoryLogger(Broker.log))
var audit_log:Log = Broker
var security_log:Log = Broker
var connection_log:Log = Broker
var console_log:Log = Broker
var services = Map[CustomServiceDTO, Service]()
override def toString() = "broker: "+id
var authenticator:Authenticator = _
var authorizer = Authorizer()
def resource_kind = SecuredResource.BrokerKind
// Also provide Runnable based interfaces so that it's easier to use from Java.
def update(config: BrokerDTO, on_completed:Runnable):Unit = update(config, new TaskWrapper(on_completed))
def start(on_completed:Runnable):Unit = super.start(new TaskWrapper(on_completed))
def stop(on_completed:Runnable):Unit = super.stop(new TaskWrapper(on_completed))
override def start(on_completed:Task):Unit = super.start(on_completed)
override def stop(on_completed:Task):Unit = super.stop(on_completed)
/**
* Validates and then applies the configuration.
*/
def update(config: BrokerDTO, on_completed:Task):Unit = dispatch_queue {
dispatch_queue.assertExecuting()
this.config = config
val tracker = new LoggingTracker("broker reconfiguration", console_log, SERVICE_TIMEOUT)
if( service_state.is_started ) {
apply_update(tracker)
}
tracker.callback(on_completed)
}
override def _start(on_completed:Task) = {
// create the runtime objects from the config
this.id = Option(config.id).getOrElse("default")
init_logs
log_versions
check_file_limit
BrokerRegistry.add(this)
schedule_reoccurring(100, MILLISECONDS) {
Broker.now = System.currentTimeMillis
}
schedule_reoccurring(1, SECONDS) {
virtualhost_maintenance
roll_current_period
tune_send_receive_buffers
}
val tracker = new LoggingTracker("broker startup", console_log, SERVICE_TIMEOUT)
apply_update(tracker)
tracker.callback(on_completed)
}
def _stop(on_completed:Task): Unit = {
val tracker = new LoggingTracker("broker shutdown", console_log, SERVICE_TIMEOUT)
// Stop the services...
services.values.foreach(tracker.stop(_))
services = Map()
// Stop accepting connections..
connectors.values.foreach( x=>
tracker.stop(x)
)
connectors.clear()
// stop the connections..
connections.valuesIterator.foreach { connection=>
tracker.stop(connection)
}
connections.clear()
// Shutdown the virtual host services
virtual_hosts.valuesIterator.foreach( x=>
tracker.stop(x)
)
virtual_hosts.clear()
virtual_hosts_by_hostname.clear()
cow_virtual_hosts_by_hostname = Map()
Option(web_server).foreach(tracker.stop(_))
web_server = null
BrokerRegistry.remove(this)
tracker.callback(on_completed)
}
def roll_current_period = {
stats_of_5min += current_period
current_period = new PeriodStat
current_period.max_connections = connections.size
max_connections_in_5min = PeriodStat(stats_of_5min).max_connections
}
def tune_send_receive_buffers = {
max_connections_in_5min = max_connections_in_5min.max(current_period.max_connections)
if ( max_connections_in_5min == 0 ) {
auto_tuned_send_receiver_buffer_size = 64*1024
} else {
// We start with the JVM heap.
var x = MAX_JVM_HEAP_SIZE
// Lets only use 1/8th of the heap for connection buffers.
x = x / 8
// 1/2 for send buffers, the other 1/2 for receive buffers.
x = x / 2
// Ok, how much space can we use per connection?
x = x / max_connections_in_5min
// Drop the bottom bits so that we are working /w 1k increments.
x = x & 0xFFFFFF00
// Constrain the result to be between a 2k and a 64k buffer.
auto_tuned_send_receiver_buffer_size = x.toInt.max(1024*2).min(1024*64)
}
// Basically this means that we will use a 64k send/receive buffer
// for the first 1024 connections established and then the buffer
// size will start getting reduced down until it gets to 2k buffers.
// Which will occur when you get to about 32,000 connections.
for( connector <- connectors.values ) {
connector.update_buffer_settings
}
}
def virtualhost_maintenance = {
val active_sessions = connections.values.flatMap(_.session_id).toSet
virtual_hosts.values.foreach { host=>
host.dispatch_queue {
if(host.service_state.is_started) {
host.router.remove_temp_destinations(active_sessions)
}
}
}
}
protected def init_logs = {
import OptionSupport._
// Configure the logging categories...
val log_category = config.log_category.getOrElse(new LogCategoryDTO)
val base_category = "org.apache.activemq.apollo.log."
security_log = Log(log_category.security.getOrElse(base_category + "security"))
audit_log = Log(log_category.audit.getOrElse(base_category + "audit"))
connection_log = Log(log_category.connection.getOrElse(base_category + "connection"))
console_log = Log(log_category.console.getOrElse(base_category + "console"))
}
protected def apply_update(tracker:LoggingTracker) {
import OptionSupport._
init_logs
key_storage = if (config.key_storage != null) {
new KeyStorage(config.key_storage)
} else {
null
}
SecurityFactory.install(this)
val host_config_by_id = HashMap[AsciiBuffer, VirtualHostDTO]()
config.virtual_hosts.foreach{ value =>
host_config_by_id += ascii(value.id) -> value
}
diff(virtual_hosts.keySet.toSet, host_config_by_id.keySet.toSet) match { case (added, updated, removed) =>
removed.foreach { id =>
for( host <- virtual_hosts.remove(id) ) {
host.config.host_names.foreach { name =>
virtual_hosts_by_hostname.remove(ascii(name))
}
tracker.stop(host)
}
}
updated.foreach { id=>
for( host <- virtual_hosts.get(id); config <- host_config_by_id.get(id) ) {
host.config.host_names.foreach { name =>
virtual_hosts_by_hostname.remove(ascii(name))
}
if( host.config.getClass == config.getClass ) {
host.update(config, tracker.task("update: "+host))
config.host_names.foreach { name =>
virtual_hosts_by_hostname += ascii(name) -> host
}
} else {
// The dto type changed.. so we have to re-create
val on_completed = tracker.task("recreate virtual host: "+id)
host.stop(^{
val host = VirtualHostFactory.create(this, config)
if( host == null ) {
console_log.warn("Could not create virtual host: "+config.id);
on_completed.run()
} else {
config.host_names.foreach { name =>
virtual_hosts_by_hostname += ascii(name) -> host
}
host.start(on_completed)
}
})
}
}
}
added.foreach { id=>
for( config <- host_config_by_id.get(id) ) {
val host = VirtualHostFactory.create(this, config)
if( host == null ) {
console_log.warn("Could not create virtual host: "+config.id);
} else {
virtual_hosts += ascii(config.id) -> host
// add all the host names of the virtual host to the virtual_hosts_by_hostname map..
config.host_names.foreach { name =>
virtual_hosts_by_hostname += ascii(name) -> host
}
tracker.start(host)
}
}
}
}
cow_virtual_hosts_by_hostname = virtual_hosts_by_hostname.toMap
// first defined host is the default virtual host
config.virtual_hosts.headOption.map(x=>ascii(x.id)).foreach { id =>
default_virtual_host = virtual_hosts.get(id).getOrElse(null)
}
val connector_config_by_id = LinkedHashMap[String, ConnectorTypeDTO]()
config.connectors.foreach{ value =>
connector_config_by_id += value.id -> value
}
diff(connectors.keySet.toSet, connector_config_by_id.keySet.toSet) match { case (added, updated, removed) =>
removed.foreach { id =>
for( connector <- connectors.remove(id) ) {
tracker.stop(connector)
}
}
updated.foreach { id=>
for( connector <- connectors.get(id); config <- connector_config_by_id.get(id) ) {
if( connector.config.getClass == config.getClass ) {
connector.update(config, tracker.task("update: "+connector))
} else {
// The dto type changed.. so we have to re-create the connector.
val on_completed = tracker.task("recreate connector: "+id)
connector.stop(^{
val connector = ConnectorFactory.create(this, config)
if( connector == null ) {
console_log.warn("Could not create connector: "+config.id);
on_completed.run()
} else {
connectors += config.id -> connector
connector.start(on_completed)
}
})
}
}
}
added.foreach { id=>
for( config <- connector_config_by_id.get(id) ) {
val connector = ConnectorFactory.create(this, config)
if( connector == null ) {
console_log.warn("Could not create connector: "+config.id);
} else {
connectors += config.id -> connector
tracker.start(connector)
}
}
}
}
val services_config = asScalaBuffer(config.services).toSet
diff(services.keySet, services_config) match { case (added, updated, removed) =>
removed.foreach { service_config =>
for( service <- services.get(service_config) ) {
services -= service_config
tracker.stop(service)
}
}
// Create the new services..
added.foreach { service_config =>
val service = CustomServiceFactory.create(this, service_config)
if( service == null ) {
console_log.warn("Could not create service: "+service_config);
} else {
services += service_config -> service
tracker.start(service)
}
}
}
if( !config.web_admins.isEmpty ) {
if ( web_server!=null ) {
web_server.update(tracker.task("update: "+web_server))
} else {
web_server = WebServerFactory.create(this)
if (web_server==null) {
warn("Could not start admistration interface.")
} else {
tracker.start(web_server)
}
}
} else {
if( web_server!=null ) {
tracker.stop(web_server)
web_server = null
}
}
}
def web_admin_url:String = {
if( web_server == null ) {
null
} else {
web_server.uris().headOption.map(_.toString.stripSuffix("/")).getOrElse(null)
}
}
private def log_versions = {
val location_info = Option(System.getProperty("apollo.home")).map { home=>
" (at: "+new File(home).getCanonicalPath+")"
}.getOrElse("")
console_log.info("OS : %s", os)
console_log.info("JVM : %s", jvm)
console_log.info("Apollo : %s%s", Broker.version, location_info)
}
private def check_file_limit:Unit = {
max_fd_limit match {
case Some(limit) =>
console_log.info("OS is restricting the open file limit to: %s", limit)
var min_limit = 500 // estimate.. perhaps could we do better?
config.connectors.foreach { connector=>
import OptionSupport._
min_limit += connector.connection_limit.getOrElse(10000)
}
if( limit < min_limit ) {
console_log.warn("Please increase the process file limit using 'ulimit -n %d' or configure lower connection limits on the broker connectors.", min_limit)
}
case None =>
}
}
def get_virtual_host(name: AsciiBuffer) = {
dispatch_queue.assertExecuting()
virtual_hosts_by_hostname.getOrElse(name, null)
}
def get_default_virtual_host = {
dispatch_queue.assertExecuting()
default_virtual_host
}
//useful for testing
def get_connect_address = {
Option(config.client_address).getOrElse {
val address= get_socket_address.asInstanceOf[InetSocketAddress]
"%s:%d".format(address.getHostName, address.getPort)
}
}
def get_socket_address = first_accepting_connector.get.socket_address
def first_accepting_connector = connectors.values.find(_.isInstanceOf[AcceptingConnector]).map(_.asInstanceOf[AcceptingConnector])
def get_socket_address(id:String) = accepting_connector(id).get.socket_address
def accepting_connector(id:String) = {
connectors.values.find( _ match {
case connector:AcceptingConnector => connector.id == id
case _ => false
}).map(_.asInstanceOf[AcceptingConnector])
}
def ssl_context(protocol:String) = {
val rc = SSLContext.getInstance(protocol);
if( key_storage!=null ) {
rc.init(key_storage.create_key_managers, key_storage.create_trust_managers, null);
} else {
rc.init(null, null, null);
}
rc
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy