/*
* Quasar: lightweight threads and actors for the JVM.
* Copyright (c) 2013-2014, Parallel Universe Software Co. All rights reserved.
*
* This program and the accompanying materials are dual-licensed under
* either the terms of the Eclipse Public License v1.0 as published by
* the Eclipse Foundation
*
* or (per the licensee's choosing)
*
* under the terms of the GNU Lesser General Public License version 3.0
* as published by the Free Software Foundation.
*/
package co.paralleluniverse.actors.behaviors;
import co.paralleluniverse.actors.Actor;
import co.paralleluniverse.actors.ActorRef;
import co.paralleluniverse.actors.MailboxConfig;
import co.paralleluniverse.actors.behaviors.Server.ServerRequest;
import co.paralleluniverse.fibers.FiberFactory;
import co.paralleluniverse.fibers.FiberScheduler;
import co.paralleluniverse.fibers.SuspendExecution;
import co.paralleluniverse.strands.Strand;
import co.paralleluniverse.strands.StrandFactory;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A {@link BehaviorActor behavior} implementing a <i>server<i/> that responds to request messages.
* The {@code ServerActor}'s behavior is implemented by either 1) subclassing this class and overriding some or all of:
* {@link #init() init}, {@link #terminate(java.lang.Throwable) terminate},
* {@link #handleCall(ActorRef, Object, Object) handleCall}, {@link #handleCast(ActorRef, Object, Object) handleCast},
* {@link #handleInfo(Object) handleInfo}, and {@link #handleTimeout() handleTimeout};
* or 2) providing an instance of {@link ServerHandler} which implements these methods to the constructor.
*
* @author pron
*/
public class ServerActor<CallMessage, V, CastMessage> extends BehaviorActor {
protected static Object NULL_RETURN_VALUE = new Object();
private static final Logger LOG = LoggerFactory.getLogger(ServerActor.class);
private TimeUnit timeoutUnit;
private long timeout;
/**
* Creates a new server actor
*
* @param name the actor name (may be {@code null}).
* @param server an optional delegate object that implements this server actor's behavior if this class is not subclassed. May be {@code null}.
* @param timeout the duration after which, if a request has not been received, the {@link #handleTimeout()} method will be called.
* @param unit {@code timeout}'s time unit. {@code null} if no timeout is to be set.
* @param strand this actor's strand.
* @param mailboxConfig this actor's mailbox settings.
*/
public ServerActor(String name, ServerHandler<CallMessage, V, CastMessage> server, long timeout, TimeUnit unit, Strand strand, MailboxConfig mailboxConfig) {
super(name, server, strand, mailboxConfig);
this.timeoutUnit = timeout > 0 ? unit : null;
this.timeout = timeout;
}
//<editor-fold defaultstate="collapsed" desc="Behavior boilerplate">
/////////// Behavior boilerplate ///////////////////////////////////
@Override
protected Server<CallMessage, V, CastMessage> makeRef(ActorRef<Object> ref) {
return new Server<CallMessage, V, CastMessage>(ref);
}
@Override
public Server<CallMessage, V, CastMessage> ref() {
return (Server<CallMessage, V, CastMessage>) super.ref();
}
@Override
protected Server<CallMessage, V, CastMessage> self() {
return ref();
}
@Override
public Server<CallMessage, V, CastMessage> spawn(StrandFactory sf) {
return (Server<CallMessage, V, CastMessage>) super.spawn(sf);
}
@Override
public Server<CallMessage, V, CastMessage> spawn(FiberFactory ff) {
return (Server<CallMessage, V, CastMessage>) super.spawn(ff);
}
@Override
public Server<CallMessage, V, CastMessage> spawn() {
return (Server<CallMessage, V, CastMessage>) super.spawn();
}
@Override
public Server<CallMessage, V, CastMessage> spawnThread() {
return (Server<CallMessage, V, CastMessage>) super.spawnThread();
}
public static <CallMessage, V, CastMessage> ServerActor<CallMessage, V, CastMessage> currentServerActor() {
return (ServerActor<CallMessage, V, CastMessage>) Actor.<Object, Void>currentActor();
}
@Override
public Logger log() {
return LOG;
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc="Constructors">
/////////// Constructors ///////////////////////////////////
/**
* Creates a new server actor
*
* @param name the actor name (may be {@code null}).
* @param server an optional delegate object that implements this server actor's behavior if this class is not subclassed. May be {@code null}.
* @param mailboxConfig this actor's mailbox settings.
*/
public ServerActor(String name, ServerHandler<CallMessage, V, CastMessage> server, MailboxConfig mailboxConfig) {
this(name, server, -1, null, null, mailboxConfig);
}
/**
* Creates a new server actor
*
* @param name the actor name (may be {@code null}).
* @param server an optional delegate object that implements this server actor's behavior if this class is not subclassed. May be {@code null}.
*/
public ServerActor(String name, ServerHandler<CallMessage, V, CastMessage> server) {
this(name, server, -1, null, null, null);
}
/**
* Creates a new server actor
*
* @param server an optional delegate object that implements this server actor's behavior if this class is not subclassed. May be {@code null}.
* @param mailboxConfig this actor's mailbox settings.
*/
public ServerActor(ServerHandler<CallMessage, V, CastMessage> server, MailboxConfig mailboxConfig) {
this(null, server, -1, null, null, mailboxConfig);
}
/**
* Creates a new server actor
*
* @param server an optional delegate object that implements this server actor's behavior if this class is not subclassed. May be {@code null}.
*/
public ServerActor(ServerHandler<CallMessage, V, CastMessage> server) {
this(null, server, -1, null, null, null);
}
/**
* Creates a new server actor
*
* @param name the actor name (may be {@code null}).
* @param mailboxConfig this actor's mailbox settings.
*/
public ServerActor(String name, MailboxConfig mailboxConfig) {
this(name, null, -1, null, null, mailboxConfig);
}
/**
* Creates a new server actor
*
* @param name the actor name (may be {@code null}).
*/
public ServerActor(String name) {
this(name, null, -1, null, null, null);
}
/**
* Creates a new server actor
*
* @param mailboxConfig this actor's mailbox settings.
*/
public ServerActor(MailboxConfig mailboxConfig) {
this(null, null, -1, null, null, mailboxConfig);
}
/**
* Creates a new server actor
*/
public ServerActor() {
this(null, null, -1, null, null, null);
}
//</editor-fold>
/**
* The {@link ServerHandler} passed at construction, or {@code null} if none was set.
*/
protected ServerHandler<CallMessage, V, CastMessage> server() {
return (ServerHandler<CallMessage, V, CastMessage>) getInitializer();
}
@Override
protected final void behavior() throws InterruptedException, SuspendExecution {
while (isRunning()) {
checkCodeSwap();
Object m1 = receive(timeout, timeoutUnit);
if (m1 == null)
handleTimeout();
else
handleMessage(m1);
}
}
/**
* {@inheritDoc}
* <p/>
* This method implements the {@code ServerActor} behavior, dispatching message to {@link #handleCall(ActorRef, Object, Object) handleCall},
* {@link #handleCast(ActorRef, Object, Object) handleCast} or {@link #handleInfo(Object) handleInfo} as appropriate.
*/
@Override
protected void handleMessage(Object m) throws InterruptedException, SuspendExecution {
if (m instanceof ServerRequest) {
ServerRequest r = (ServerRequest) m;
switch (r.getType()) {
case CALL:
try {
final V res = handleCall((ActorRef<V>) r.getFrom(), r.getId(), (CallMessage) r.getMessage());
if (res != null)
reply((ActorRef<V>) r.getFrom(), r.getId(), res == NULL_RETURN_VALUE ? null : res);
} catch (Exception e) {
replyError((ActorRef<V>) r.getFrom(), r.getId(), e);
}
break;
case CAST:
handleCast((ActorRef<V>) r.getFrom(), r.getId(), (CastMessage) r.getMessage());
break;
}
} else
handleInfo(m);
}
@Override
public void shutdown() {
super.shutdown();
}
/**
* Sets a duration after which, if a request has not been received, the {@link #handleTimeout()} method will be called.
* The time count is reset after every received message. This method will be triggered multiple times if a message is not received
* for a period of time longer than multiple timeout durations.
*
* @param timeout the timeout duration
* @param unit {@code timeout}'s time unit; {@code null} if the timeout is to be unset.
*/
public final void setTimeout(long timeout, TimeUnit unit) {
verifyInActor();
this.timeoutUnit = timeout > 0 ? unit : null;
this.timeout = timeout;
}
/**
* Called to handle a synchronous request (one waiting for a response).
* <p/>
* By default, this method calls {@link #server() server}.{@link ServerHandler#handleCall(ActorRef, Object, Object) handleCall} if a server object was supplied
* at construction time. Otherwise, it throws an {@link UnsupportedOperationException}, which will be sent back to the requester.
* <ul>
* <li>If this method returns a non-null value, it will be sent back to the sender of the request wrapped by an {@link ErrorResponseMessage};
* if the request was sent via {@link Server#call(Object) Server.call} (which is how it's usually done), this value will be returned
* by the {@link Server#call(java.lang.Object) call} method.</li>
* <li>If this method throws an exception, it will be sent back to the sender of the request wrapped by an {@link ErrorResponseMessage};
* if the request was sent via {@link Server#call(Object) Server.call}, the exception will be thrown by the {@link Server#call(java.lang.Object) call}
* method, possibly wrapped in a {@link RuntimeException}.</li>
* <li>If this method returns {@code null}, then a reply is not immediately sent, and the {@link Server#call(java.lang.Object) call} method
* will remain blocked until a reply is sent manually with {@link #reply(ActorRef, Object, Object) reply} or
* {@link #replyError(ActorRef, Object, Throwable) replyError}.</li>
* </ul>
*
* @param from the sender of the request
* @param id the request's unique id
* @param m the request
* @return a value that will be sent as a response to the sender of the request.
* @throws Exception if thrown, it will be sent back to the sender of the request.
*/
protected V handleCall(ActorRef<?> from, Object id, CallMessage m) throws Exception, SuspendExecution {
if (server() != null)
return server().handleCall(from, id, m);
else
throw new UnsupportedOperationException(m.toString());
}
/**
* Replies with a result to a call request, if the {@link #handleCall(ActorRef, Object, Object) handleCall} method returned null.
* <p/>
* If the request has been sent by a call to {@link Server#call(Object) Server.call} (which is how it's usually done), the
* {@code result} argument will be the value returned by {@link Server#call(Object) call}.
* <p/>
* This method can only be called by this actor.
* <p/>
* Internally this method uses a {@link ValueResponseMessage} to send the reply.
*
* @param to the actor we're responding to (the request's sender)
* @param id the request's identifier
* @param value the result of the request
*/
public final void reply(ActorRef<?> to, Object id, V value) throws SuspendExecution {
verifyInActor();
((ActorRef) to).send(new ValueResponseMessage<V>(id, value));
}
/**
* Replies with an exception to a call request, if the {@link #handleCall(ActorRef, Object, Object) handleCall} method returned null.
* If the request has been sent by a call to {@link Server#call(Object) Server.call} (which is how it's usually done), the
* {@code e} argument will be the exception thrown by {@link Server#call(Object) call} (possibly wrapped by a {@link RuntimeException}).
* <p/>
* This method can only be called by this actor.
* <p/>
* Internally this method uses an {@link ErrorResponseMessage} to send the reply.
*
* @param to the actor we're responding to (the request's sender)
* @param id the request's identifier
* @param error the error the request has caused
*/
public final void replyError(ActorRef<?> to, Object id, Throwable error) throws SuspendExecution {
verifyInActor();
((ActorRef) to).send(new ErrorResponseMessage(id, error));
}
/**
* Called to handle an asynchronous request (one that does not for a response).
* <p/>
* By default, this method calls {@link #server() server}.{@link ServerHandler#handleCast(ActorRef, Object, Object) handleCast} if a server object was supplied
* at construction time. Otherwise, it throws an {@link UnsupportedOperationException}, which will result in this actor's death, unless caught.
*
* @param from the sender of the request
* @param id the request's unique id
* @param m the request
*/
protected void handleCast(ActorRef<?> from, Object id, CastMessage m) throws SuspendExecution {
if (server() != null)
server().handleCast(from, id, m);
else
throw new UnsupportedOperationException(m.toString());
}
/**
* Called to handle any message sent to this actor that is neither a {@link #handleCall(ActorRef, Object, Object) call} nor a {@link #handleCast(ActorRef, Object, Object) cast}.
* <p/>
* By default, this method calls {@link #server() server}.{@link ServerHandler#handleInfo(Object) handleInfo} if a server object was supplied
* at construction time. Otherwise, it does nothing.
*
* @param m the message
*/
protected void handleInfo(Object m) throws SuspendExecution {
if (server() != null)
server().handleInfo(m);
}
/**
* Called whenever the timeout set with {@link #setTimeout(long, TimeUnit) setTimeout} or supplied at construction expires without any message
* received. The countdown is reset after every received message. This method will be triggered multiple times if a message is not received
* for a period of time longer than multiple timeout durations.
* <p/>
* By default, this method calls {@link #server() server}.{@link ServerHandler#handleTimeout() handleTimeout} if a server object was supplied
* at construction time. Otherwise, it does nothing.
*/
protected void handleTimeout() throws SuspendExecution {
if (server() != null)
server().handleTimeout();
}
}