/*
* Copyright (c) 1998-2011 Caucho Technology -- all rights reserved
*
* This file is part of Resin(R) Open Source
*
* Each copy or derived work must preserve the copyright notice and this
* notice unmodified.
*
* Resin Open Source 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; either version 2 of the License, or
* (at your option) any later version.
*
* Resin Open Source 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, or any warranty
* of NON-INFRINGEMENT. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with Resin Open Source; if not, write to the
*
* Free Software Foundation, Inc.
* 59 Temple Place, Suite 330
* Boston, MA 02111-1307 USA
*
* @author Scott Ferguson
*/
package com.caucho.hemp.broker;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.enterprise.inject.spi.Bean;
import com.caucho.bam.BamError;
import com.caucho.bam.actor.ActorHolder;
import com.caucho.bam.broker.AbstractManagedBroker;
import com.caucho.bam.broker.Broker;
import com.caucho.bam.mailbox.Mailbox;
import com.caucho.bam.mailbox.MultiworkerMailbox;
import com.caucho.bam.mailbox.PassthroughMailbox;
import com.caucho.bam.packet.Message;
import com.caucho.bam.packet.MessageError;
import com.caucho.bam.packet.Packet;
import com.caucho.bam.packet.Query;
import com.caucho.bam.packet.QueryError;
import com.caucho.bam.packet.QueryResult;
import com.caucho.bam.stream.MessageStream;
import com.caucho.config.inject.InjectManager;
import com.caucho.env.service.AfterResinStartListener;
import com.caucho.env.service.ResinSystem;
import com.caucho.loader.Environment;
import com.caucho.loader.EnvironmentClassLoader;
import com.caucho.loader.EnvironmentListener;
import com.caucho.loader.EnvironmentLocal;
import com.caucho.server.admin.AdminService;
import com.caucho.util.Alarm;
import com.caucho.util.Base64;
import com.caucho.util.L10N;
/**
* Broker
*/
public class HempBroker extends AbstractManagedBroker
{
private static final Logger log
= Logger.getLogger(HempBroker.class.getName());
private static final L10N L = new L10N(HempBroker.class);
private final static EnvironmentLocal<HempBroker> _localBroker
= new EnvironmentLocal<HempBroker>();
private final AtomicLong _addressGenerator
= new AtomicLong(Alarm.getCurrentTime());
private HempBrokerManager _manager;
private DomainManager _domainManager;
// actors and clients
private final
ConcurrentHashMap<String,WeakReference<Mailbox>> _actorStreamMap
= new ConcurrentHashMap<String,WeakReference<Mailbox>>();
// permanent registered actors
private final HashMap<String,Mailbox> _actorMap
= new HashMap<String,Mailbox>();
private final Map<String,WeakReference<Mailbox>> _actorCache
= Collections.synchronizedMap(new HashMap<String,WeakReference<Mailbox>>());
private String _domain = "localhost";
private String _managerAddress = "localhost";
private ArrayList<String> _aliasList = new ArrayList<String>();
private ArrayList<Packet> _startupPacketList = new ArrayList<Packet>();
private ResinSystem _resinSystem;
/*
private BrokerListener []_actorManagerList
= new BrokerListener[0];
*/
private volatile boolean _isClosed;
public HempBroker(HempBrokerManager manager)
{
_resinSystem = manager.getResinSystem();
_manager = manager;
Environment.addCloseListener(this);
if (_localBroker.getLevel() == null)
_localBroker.set(this);
if (_resinSystem != null)
_resinSystem.addListener(new AfterStartListener(this));
}
public HempBroker(HempBrokerManager manager, String domain)
{
this(manager);
_domain = domain;
_managerAddress = domain;
}
public static HempBroker getCurrent()
{
return _localBroker.get();
}
public void setDomainManager(DomainManager domainManager)
{
_domainManager = domainManager;
}
/**
* Returns true if the broker is closed
*/
@Override
public boolean isClosed()
{
return _isClosed;
}
/**
* Adds a domain alias
*/
public void addAlias(String domain)
{
_aliasList.add(domain);
}
public void afterStart()
{
deliverStartupPackets();
ArrayList<Packet> deadPackets = new ArrayList<Packet>(_startupPacketList);
_startupPacketList.clear();
for (Packet packet : deadPackets) {
packet.dispatch(this, this);
}
}
//
// API
//
protected String generateAddress(String uid, String resource)
{
StringBuilder sb = new StringBuilder();
if (uid == null)
uid = "anonymous";
if (uid.indexOf('@') > 0)
sb.append(uid);
else
sb.append(uid).append('@').append(getDomain());
sb.append("/");
if (resource != null)
sb.append(resource);
else {
Base64.encode(sb, _addressGenerator.incrementAndGet());
}
return sb.toString();
}
/**
* Registers a actor
*/
@Override
public void addMailbox(Mailbox mailbox)
{
String address = mailbox.getAddress();
synchronized (_actorMap) {
Mailbox oldMailbox = _actorMap.get(address);
if (oldMailbox != null)
throw new IllegalStateException(L.l("duplicated address='{0}' is not allowed",
address));
_actorMap.put(address, mailbox);
}
synchronized (_actorStreamMap) {
WeakReference<Mailbox> oldRef = _actorStreamMap.get(address);
if (oldRef != null && oldRef.get() != null)
throw new IllegalStateException(L.l("duplicated address='{0}' is not allowed",
address));
_actorStreamMap.put(address, new WeakReference<Mailbox>(mailbox));
}
if (log.isLoggable(Level.FINEST))
log.finest(this + " addMailbox address=" + address + " " + mailbox);
// if in startup phase, deliver the queued messages
if (isBeforeActive()) {
deliverStartupPackets();
}
}
/**
* Removes a actor
*/
@Override
public void removeMailbox(Mailbox mailbox)
{
String address = mailbox.getAddress();
synchronized (_actorMap) {
_actorMap.remove(address);
}
synchronized (_actorStreamMap) {
_actorStreamMap.remove(address);
}
if (log.isLoggable(Level.FINE))
log.fine(this + " removeActor address=" + address + " " + mailbox);
}
/**
* Returns the manager's own id.
*/
protected String getManagerAddress()
{
return _managerAddress;
}
/**
* Returns the domain
*/
protected String getDomain()
{
return _domain;
}
/**
* getAddress() returns null for the broker
*/
@Override
public String getAddress()
{
return _domain;
}
//
// state methods
//
private boolean isBeforeActive()
{
if (_resinSystem != null)
return _resinSystem.isBeforeActive();
else
return false;
}
//
// packet methods
//
/**
* Sends a message to the desination mailbox.
*/
@Override
public void message(String to, String from, Serializable payload)
{
Mailbox mailbox = getMailbox(to);
if (mailbox != null) {
mailbox.message(to, from, payload);
return;
}
// on startup, queue the messages until the startup completes
if (isBeforeActive() && addStartupPacket(new Message(to, from, payload))) {
// startup packets are successful
}
else {
// use default error handling
super.message(to, from, payload);
}
}
/**
* Sends a messageError to the desination mailbox.
*/
@Override
public void messageError(String to,
String from,
Serializable payload,
BamError error)
{
Mailbox mailbox = getMailbox(to);
if (mailbox != null) {
mailbox.messageError(to, from, payload, error);
return;
}
// on startup, queue the messages until the startup completes
if (isBeforeActive()
&& addStartupPacket(new MessageError(to, from, payload, error))) {
// startup packets are successful
}
else {
// use default error handling
super.messageError(to, from, payload, error);
}
}
/**
* Sends a query to the destination mailbox.
*/
@Override
public void query(long id, String to, String from, Serializable payload)
{
Mailbox mailbox = getMailbox(to);
if (mailbox != null) {
mailbox.query(id, to, from, payload);
return;
}
// on startup, queue the messages until the startup completes
if (isBeforeActive()
&& addStartupPacket(new Query(id, to, from, payload))) {
// startup packets are successful
}
else {
// use default error handling
super.query(id, to, from, payload);
}
}
/**
* Sends a query to the destination mailbox.
*/
@Override
public void queryResult(long id, String to, String from, Serializable payload)
{
Mailbox mailbox = getMailbox(to);
if (mailbox != null) {
mailbox.queryResult(id, to, from, payload);
return;
}
// on startup, queue the messages until the startup completes
if (isBeforeActive()
&& addStartupPacket(new QueryResult(id, to, from, payload))) {
// startup packets are successful
}
else {
// use default error handling
super.queryResult(id, to, from, payload);
}
}
/**
* Sends a query to the destination mailbox.
*/
@Override
public void queryError(long id, String to, String from, Serializable payload,
BamError error)
{
Mailbox mailbox = getMailbox(to);
if (mailbox != null) {
mailbox.queryError(id, to, from, payload, error);
return;
}
// on startup, queue the messages until the startup completes
if (isBeforeActive()
&& addStartupPacket(new QueryError(id, to, from, payload, error))) {
// startup packets are successful
}
else {
// use default error handling
super.queryError(id, to, from, payload, error);
}
}
private boolean addStartupPacket(Packet packet)
{
synchronized (_startupPacketList) {
_startupPacketList.add(packet);
}
deliverStartupPackets();
return true;
}
private void deliverStartupPackets()
{
Packet packet;
while ((packet = extractStartupPacket()) != null) {
Mailbox mailbox = getMailbox(packet.getTo());
if (mailbox != null)
packet.dispatch(mailbox, this);
else {
log.warning(this + " failed to find mailbox " + packet.getTo()
+ " for " + packet);
}
}
}
/**
* Return a queued packet that has an active mailbox.
*/
private Packet extractStartupPacket()
{
synchronized (_startupPacketList) {
int size = _startupPacketList.size();
for (int i = 0; i < size; i++) {
Packet packet = _startupPacketList.get(i);
Mailbox mailbox = getMailbox(packet.getTo());
if (mailbox != null) {
_startupPacketList.remove(i);
return packet;
}
}
}
return null;
}
//
// mailbox methods
//
/**
* Returns the mailbox for the given address
*/
@Override
public Mailbox getMailbox(String address)
{
if (address == null)
return null;
WeakReference<Mailbox> ref = _actorStreamMap.get(address);
if (ref != null) {
Mailbox mailbox = ref.get();
if (mailbox != null)
return mailbox;
}
if (address.endsWith("@")) {
// jms/3d00
address = address + getDomain();
}
return putActorStream(address, findDomain(address));
}
private Mailbox putActorStream(String address, Mailbox actorStream)
{
if (actorStream == null)
return null;
synchronized (_actorStreamMap) {
WeakReference<Mailbox> ref = _actorStreamMap.get(address);
if (ref != null)
return ref.get();
_actorStreamMap.put(address, new WeakReference<Mailbox>(actorStream));
return actorStream;
}
}
private Mailbox findDomain(String domain)
{
if (domain == null)
return null;
if ("local".equals(domain))
return getBrokerMailbox();
Broker broker = null;
if (_manager != null)
broker = _manager.findBroker(domain);
if (broker == this)
return null;
Mailbox stream = null;
if (_domainManager != null)
stream = _domainManager.findDomain(domain);
return stream;
}
protected boolean startActorFromManager(String address)
{
/*
for (BrokerListener manager : _actorManagerList) {
if (manager.startActor(address))
return true;
}
*/
return false;
}
/**
* Closes a connection
*/
void closeActor(String address)
{
int p = address.indexOf('/');
if (p > 0) {
String owner = address.substring(0, p);
ActorHolder actor = null;
/*
if (actor != null) {
try {
actor.onChildStop(address);
} catch (Exception e) {
log.log(Level.FINE, e.toString(), e);
}
}
*/
}
_actorCache.remove(address);
synchronized (_actorStreamMap) {
_actorStreamMap.remove(address);
}
}
//
// CDI callbacks
//
public void addStartupActor(Bean bean,
String name,
int threadMax)
{
ActorStartup startup
= new ActorStartup(bean, name, threadMax);
Environment.addEnvironmentListener(startup);
}
private void startActor(Bean bean,
String name,
int threadMax)
{
InjectManager beanManager = InjectManager.getCurrent();
ActorHolder actor = (ActorHolder) beanManager.getReference(bean);
actor.setBroker(this);
String address = name;
if (address == null || "".equals(address))
address = bean.getName();
if (address == null || "".equals(address))
address = bean.getBeanClass().getSimpleName();
if (address.indexOf('@') < 0)
address = address + '@' + getAddress();
else if (address.endsWith("@"))
address = address.substring(0, address.length() - 1);
actor.setAddress(address);
ActorHolder bamActor = actor;
Mailbox mailbox;
// queue
if (threadMax > 0) {
MessageStream actorStream = bamActor.getActor();
mailbox = new MultiworkerMailbox(address, actorStream, this, threadMax);
// bamActor.setActorStream(actorStream);
}
else {
mailbox = new PassthroughMailbox(address, bamActor.getActor(), this);
}
addMailbox(mailbox);
Environment.addCloseListener(new ActorClose(mailbox));
}
private void startActor(Bean bean, AdminService bamService)
{
InjectManager beanManager = InjectManager.getCurrent();
ActorHolder actor = (ActorHolder) beanManager.getReference(bean);
actor.setBroker(this);
String address = bamService.name();
if (address == null || "".equals(address))
address = bean.getName();
if (address == null || "".equals(address))
address = bean.getBeanClass().getSimpleName();
actor.setAddress(address);
int threadMax = bamService.threadMax();
ActorHolder bamActor = actor;
Mailbox mailbox = null;
// queue
if (threadMax > 0) {
MessageStream actorStream = bamActor.getActor();
mailbox = new MultiworkerMailbox(address, actorStream, this, threadMax);
bamActor.setMailbox(mailbox);
}
addMailbox(mailbox);
Environment.addCloseListener(new ActorClose(mailbox));
}
public void close()
{
_isClosed = true;
_manager.removeBroker(_domain);
for (String alias : _aliasList)
_manager.removeBroker(alias);
ArrayList<Mailbox> mailboxes = new ArrayList<Mailbox>(_actorMap.values());
_actorMap.clear();
_actorCache.clear();
_actorStreamMap.clear();
for (Mailbox mailbox : mailboxes) {
try {
mailbox.close();
} catch (Throwable e) {
log.log(Level.FINE, e.toString(), e);
}
}
}
private String getAddress(ActorHolder actor, Annotation []annList)
{
com.caucho.remote.BamService bamAnn = findActor(annList);
String name = "";
if (bamAnn != null)
name = bamAnn.name();
if (name == null || "".equals(name))
name = actor.getAddress();
if (name == null || "".equals(name))
name = actor.getClass().getSimpleName();
String address = name;
if (address.indexOf('@') < 0 && address.indexOf('/') < 0)
address = name + "@" + getAddress();
return address;
}
private int getThreadMax(Annotation []annList)
{
com.caucho.remote.BamService bamAnn = findActor(annList);
if (bamAnn != null)
return bamAnn.threadMax();
else
return 1;
}
private com.caucho.remote.BamService findActor(Annotation []annList)
{
for (Annotation ann : annList) {
if (ann.annotationType().equals(com.caucho.remote.BamService.class))
return (com.caucho.remote.BamService) ann;
// XXX: stereotypes
}
return null;
}
@Override
public String toString()
{
return getClass().getSimpleName() + "[" + _domain + "]";
}
public class ActorStartup implements EnvironmentListener{
private Bean<?> _bean;
private String _name;
private int _threadMax;
ActorStartup(Bean<?> bean, String name, int threadMax)
{
_bean = bean;
_name = name;
_threadMax = threadMax;
}
Bean<?> getBean()
{
return _bean;
}
String getName()
{
return _name;
}
int getThreadMax()
{
return _threadMax;
}
public void environmentConfigure(EnvironmentClassLoader loader)
{
}
public void environmentBind(EnvironmentClassLoader loader)
{
}
public void environmentStart(EnvironmentClassLoader loader)
{
startActor(_bean, _name, _threadMax);
}
public void environmentStop(EnvironmentClassLoader loader)
{
}
}
public class ActorClose {
private Mailbox _actor;
ActorClose(Mailbox actor)
{
_actor = actor;
}
public void close()
{
removeMailbox(_actor);
}
}
static class AfterStartListener implements AfterResinStartListener {
private WeakReference<HempBroker> _brokerRef;
AfterStartListener(HempBroker broker)
{
_brokerRef = new WeakReference<HempBroker>(broker);
}
@Override
public void afterStart()
{
HempBroker broker = _brokerRef.get();
if (broker != null)
broker.afterStart();
}
}
}