/*
* 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 Emil Ong
*/
package com.caucho.bam.actor;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.ref.SoftReference;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.WeakHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.caucho.bam.BamError;
import com.caucho.bam.BamException;
import com.caucho.bam.Message;
import com.caucho.bam.MessageError;
import com.caucho.bam.Query;
import com.caucho.bam.QueryError;
import com.caucho.bam.QueryResult;
import com.caucho.bam.broker.Broker;
import com.caucho.bam.stream.MessageStream;
import com.caucho.util.L10N;
/**
* The Skeleton introspects and dispatches messages for a
* {@link com.caucho.bam.actor.SimpleActor}
* or {@link com.caucho.bam.actor.SkeletonActorFilter}.
*/
public class BamSkeleton<S>
{
private static final L10N L = new L10N(BamSkeleton.class);
private static final Logger log
= Logger.getLogger(BamSkeleton.class.getName());
private final static WeakHashMap<Class<?>, SoftReference<BamSkeleton<?>>> _skeletonRefMap
= new WeakHashMap<Class<?>, SoftReference<BamSkeleton<?>>>();
private Class<?> _cl;
private final HashMap<Class<?>, Method> _messageHandlers
= new HashMap<Class<?>, Method>();
private final HashMap<Class<?>, Method> _messageErrorHandlers
= new HashMap<Class<?>, Method>();
private final HashMap<Class<?>, QueryInvoker> _queryHandlers
= new HashMap<Class<?>, QueryInvoker>();
private final HashMap<Class<?>, Method> _queryResultHandlers
= new HashMap<Class<?>, Method>();
private final HashMap<Class<?>, Method> _queryErrorHandlers
= new HashMap<Class<?>, Method>();
private BamSkeleton(Class<S> cl)
{
_cl = cl;
log.finest(L.l("{0} introspecting class {1}", this, cl.getName()));
introspect(cl);
}
@SuppressWarnings("unchecked")
public static <T> BamSkeleton<T>
getSkeleton(Class<T> cl)
{
synchronized(_skeletonRefMap) {
SoftReference<BamSkeleton<?>> skeletonRef = _skeletonRefMap.get(cl);
BamSkeleton<?> skeleton = null;
if (skeletonRef != null)
skeleton = skeletonRef.get();
if (skeleton == null) {
skeleton = new BamSkeleton(cl);
_skeletonRefMap.put(cl, new SoftReference<BamSkeleton<?>>(skeleton));
}
return (BamSkeleton<T>) skeleton;
}
}
/**
* Dispatches a message to the actorStream.
*/
public void message(S actor,
MessageStream fallback,
String to,
String from,
Serializable payload)
{
Method handler;
if (payload != null)
handler = _messageHandlers.get(payload.getClass());
else
handler = null;
if (handler != null) {
if (log.isLoggable(Level.FINEST)) {
log.finest(actor + " message " + payload
+ " {from:" + from + ", to:" + to + "}");
}
try {
handler.invoke(actor, to, from, payload);
}
catch (RuntimeException e) {
throw e;
}
catch (InvocationTargetException e) {
Throwable cause = e.getCause();
throw SkeletonInvocationException.createRuntimeException(cause);
}
catch (Exception e) {
throw SkeletonInvocationException.createRuntimeException(e);
}
}
else {
fallback.message(to, from, payload);
}
}
public void messageError(S actor,
MessageStream fallback,
String to,
String from,
Serializable payload,
BamError error)
{
Method handler;
if (payload != null)
handler = _messageErrorHandlers.get(payload.getClass());
else
handler = null;
if (handler != null) {
if (log.isLoggable(Level.FINEST)) {
log.finest(actor + " messageError " + error + " " + payload
+ " {from:" + from + ", to:" + to + "}");
}
try {
handler.invoke(actor, to, from, payload, error);
}
catch (RuntimeException e) {
throw e;
}
catch (InvocationTargetException e) {
Throwable cause = e.getCause();
throw SkeletonInvocationException.createRuntimeException(cause);
}
catch (Exception e) {
throw SkeletonInvocationException.createRuntimeException(e);
}
}
else {
fallback.messageError(to, from, payload, error);
}
}
public void query(S actor,
MessageStream fallback,
Broker broker,
long id,
String to,
String from,
Serializable payload)
{
QueryInvoker handler;
if (payload != null)
handler = _queryHandlers.get(payload.getClass());
else {
handler = null;
}
if (handler != null) {
if (log.isLoggable(Level.FINEST)) {
log.finest(actor + " query " + payload
+ " {id: " + id + ", from:" + from + ", to:" + to + "}");
}
try {
handler.invoke(actor, broker, id, to, from, payload);
}
catch (RuntimeException e) {
// broker.queryError(id, from, to, payload, ActorError.create(e));
throw e;
}
catch (InvocationTargetException e) {
Throwable cause = e.getCause();
// broker.queryError(id, from, to, payload, ActorError.create(cause));
throw SkeletonInvocationException.createRuntimeException(cause);
}
catch (Exception e) {
// broker.queryError(id, from, to, payload, ActorError.create(e));
throw SkeletonInvocationException.createRuntimeException(e);
}
}
else {
fallback.query(id, to, from, payload);
}
}
public void queryResult(S actor,
MessageStream fallback,
long id,
String to,
String from,
Serializable payload)
{
Method handler;
if (payload != null)
handler = _queryResultHandlers.get(payload.getClass());
else
handler = null;
if (handler != null) {
if (log.isLoggable(Level.FINEST)) {
log.finest(actor + " queryResult " + payload
+ " {id: " + id + ", from:" + from + ", to:" + to + "}");
}
try {
handler.invoke(actor, id, to, from, payload);
}
catch (RuntimeException e) {
throw e;
}
catch (InvocationTargetException e) {
Throwable cause = e.getCause();
throw SkeletonInvocationException.createRuntimeException(cause);
}
catch (Exception e) {
throw SkeletonInvocationException.createRuntimeException(e);
}
}
else {
fallback.queryResult(id, to, from, payload);
}
}
public void queryError(S actor,
MessageStream fallback,
long id,
String to,
String from,
Serializable payload,
BamError error)
{
Method handler;
if (payload != null)
handler = _queryErrorHandlers.get(payload.getClass());
else
handler = null;
if (handler != null) {
if (log.isLoggable(Level.FINEST)) {
log.finest(actor + " queryError " + error + " " + payload
+ " {id: " + id + ", from:" + from + ", to:" + to + "}");
}
try {
handler.invoke(actor, id, to, from, payload, error);
}
catch (RuntimeException e) {
throw e;
}
catch (InvocationTargetException e) {
Throwable cause = e.getCause();
throw SkeletonInvocationException.createRuntimeException(cause);
}
catch (Exception e) {
throw SkeletonInvocationException.createRuntimeException(e);
}
}
else {
fallback.queryError(id, to, from, payload, error);
}
}
protected void introspect(Class<?> cl)
{
if (cl == null)
return;
introspect(cl.getSuperclass());
Method[] methods = cl.getDeclaredMethods();
for (int i = 0; i < methods.length; i++) {
Method method = methods[i];
Class<?> payloadType = getPayloadType(Message.class, method);
if (payloadType != null) {
log.log(Level.ALL, L.l("{0} introspect @Message {1} method={2}",
this, payloadType.getName(), method));
method.setAccessible(true);
_messageHandlers.put(payloadType, method);
continue;
}
payloadType = getPayloadType(MessageError.class, method);
if (payloadType != null) {
log.log(Level.ALL, L.l("{0} introspect @MessageError {1} method={2}",
this, payloadType.getName(), method));
method.setAccessible(true);
_messageErrorHandlers.put(payloadType, method);
continue;
}
payloadType = getQueryPayloadType(Query.class, method);
if (payloadType != null) {
log.log(Level.ALL, L.l("{0} @Query {1} method={2}",
this, payloadType.getName(), method));
method.setAccessible(true);
if (method.getParameterTypes().length == 1)
_queryHandlers.put(payloadType, new QueryShortMethodInvoker(method));
else if (method.getParameterTypes().length == 4)
_queryHandlers.put(payloadType, new QueryMethodInvoker(method));
else
throw new IllegalStateException(String.valueOf(method));
continue;
}
payloadType = getQueryPayloadType(QueryResult.class, method);
if (payloadType != null) {
log.log(Level.ALL, L.l("{0} @QueryResult {1} method={2}",
this, payloadType.getName(), method));
method.setAccessible(true);
_queryResultHandlers.put(payloadType, method);
continue;
}
payloadType = getQueryErrorPayloadType(QueryError.class, method);
if (payloadType != null) {
log.log(Level.ALL, L.l("{0} @QueryError {1} method={2}",
this, payloadType.getName(), method));
method.setAccessible(true);
_queryErrorHandlers.put(payloadType, method);
continue;
}
}
}
private Class<?> getPayloadType(Class<? extends Annotation> annotationType,
Method method)
{
Class<?> []paramTypes = method.getParameterTypes();
if (paramTypes.length < 3)
return null;
if (method.isAnnotationPresent(annotationType))
return paramTypes[2];
else
return null;
}
private Class<?> getQueryPayloadType(Class<? extends Annotation> annotationType,
Method method)
{
if (! method.isAnnotationPresent(annotationType))
return null;
Class<?> []paramTypes = method.getParameterTypes();
if (paramTypes.length == 1
&& Serializable.class.isAssignableFrom(paramTypes[0]))
return paramTypes[0];
else if (paramTypes.length == 4
&& long.class.equals(paramTypes[0])
&& String.class.equals(paramTypes[1])
&& String.class.equals(paramTypes[2])
&& Serializable.class.isAssignableFrom(paramTypes[3])) {
return paramTypes[3];
}
else {
throw new BamException(method + " is an invalid "
+ " @" + annotationType.getSimpleName()
+ " because queries require (long, String, String, MyPayload)");
}
}
private Class<?> getQueryErrorPayloadType(Class<? extends Annotation> annotationType, Method method)
{
if (! method.isAnnotationPresent(annotationType))
return null;
Class<?> []paramTypes = method.getParameterTypes();
if (paramTypes.length != 5
|| ! long.class.equals(paramTypes[0])
|| ! String.class.equals(paramTypes[1])
|| ! String.class.equals(paramTypes[2])
|| ! Serializable.class.isAssignableFrom(paramTypes[3])
|| ! BamError.class.isAssignableFrom(paramTypes[4])) {
throw new BamException(method + " is an invalid "
+ " @" + annotationType.getSimpleName()
+ " because queries require (long, String, String, MyPayload, ActorError)");
}
/*
else if (! void.class.equals(method.getReturnType())) {
throw new ActorException(method + " is an invalid @"
+ annotationType.getSimpleName()
+ " because queries must return void");
}
*/
return paramTypes[3];
}
@Override
public String toString()
{
return getClass().getSimpleName() + "[" + _cl.getName() + "]";
}
abstract static class QueryInvoker {
abstract public void invoke(Object actor,
Broker broker,
long id,
String to,
String from,
Serializable payload)
throws IllegalAccessException, InvocationTargetException;
}
static class QueryMethodInvoker extends QueryInvoker {
private final Method _method;
QueryMethodInvoker(Method method)
{
_method = method;
}
@Override
public void invoke(Object actor,
Broker broker,
long id,
String to,
String from,
Serializable payload)
throws IllegalAccessException, InvocationTargetException
{
_method.invoke(actor, id, to, from, payload);
}
}
static class QueryShortMethodInvoker extends QueryInvoker {
private final Method _method;
QueryShortMethodInvoker(Method method)
{
_method = method;
}
@Override
public void invoke(Object actor,
Broker broker,
long id,
String to,
String from,
Serializable payload)
throws IllegalAccessException, InvocationTargetException
{
Object result = _method.invoke(actor, payload);
broker.queryResult(id, from, to, (Serializable) result);
}
}
}