package ameba.websocket;
import ameba.core.Addon;
import ameba.core.Application;
import ameba.core.ServiceLocators;
import ameba.i18n.Messages;
import ameba.scanner.ClassFoundEvent;
import ameba.websocket.internal.DefaultServerEndpointConfig;
import ameba.websocket.internal.WebSocketBinder;
import com.google.common.collect.Lists;
import org.glassfish.hk2.api.PerLookup;
import org.glassfish.jersey.internal.inject.Binding;
import org.glassfish.jersey.internal.inject.Bindings;
import org.glassfish.jersey.internal.inject.InjectionManager;
import org.glassfish.jersey.process.internal.RequestScoped;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.websocket.DeploymentException;
import javax.websocket.server.ServerContainer;
import javax.ws.rs.core.Feature;
import javax.ws.rs.core.FeatureContext;
import java.lang.annotation.Annotation;
import java.util.List;
/**
* WebSocket add on
*
* @author icode
*/
public class WebSocketAddon extends Addon {
/**
* Constant <code>WEB_SOCKET_ENABLED_CONF="websocket.enabled"</code>
*/
public static final String WEB_SOCKET_ENABLED_CONF = "websocket.enabled";
private static final Logger logger = LoggerFactory.getLogger(WebSocketAddon.class);
private static final List<Class> endpointClasses = Lists.newArrayList();
private static Boolean enabled = null;
/**
* <p>isEnabled.</p>
*
* @return a boolean.
*/
public static Boolean isEnabled() {
return enabled;
}
/**
* <p>_getAnnotation.</p>
*
* @param annotationClass a {@link java.lang.Class} object.
* @param endpointClass a {@link java.lang.Class} object.
* @param <A> a A object.
* @return a A object.
*/
protected static <A> A _getAnnotation(Class<A> annotationClass, Class endpointClass) {
return annotationClass.cast(endpointClass.getAnnotation(annotationClass));
}
/**
* <p>getAnnotation.</p>
*
* @param annotationClass a {@link java.lang.Class} object.
* @param endpointClass a {@link java.lang.Class} object.
* @param <A> a A object.
* @return a A object.
*/
protected static <A> A getAnnotation(Class<A> annotationClass, Class endpointClass) {
if (endpointClass == Object.class || endpointClass == null) return null;
A annotation = _getAnnotation(annotationClass, endpointClass);
if (annotation == null) {
Class sCls = endpointClass.getSuperclass();
if (sCls != null) {
annotation = _getAnnotation(annotationClass, sCls);
}
if (annotation == null) {
Class[] inces = endpointClass.getInterfaces();
for (Class infc : inces) {
annotation = _getAnnotation(annotationClass, infc);
if (annotation != null) {
return annotation;
}
}
annotation = getAnnotation(annotationClass, sCls);
if (annotation == null) {
for (Class infc : inces) {
annotation = getAnnotation(annotationClass, infc);
if (annotation != null) {
return annotation;
}
}
}
}
}
return annotation;
}
/**
* {@inheritDoc}
*/
public boolean isEnabled(Application application) {
if (enabled == null) {
enabled = !"false".equals(application.getSrcProperties().get(WEB_SOCKET_ENABLED_CONF));
if (!enabled) {
logger.info(Messages.get("web.socket.info.disabled"));
}
}
return enabled;
}
/**
* {@inheritDoc}
*/
@Override
public void setup(final Application application) {
endpointClasses.clear();
subscribeSystemEvent(ClassFoundEvent.class, event -> event.accept(info -> {
if (info.accpet(ctClass -> ctClass.hasAnnotation(WebSocket.class))) {
endpointClasses.add(info.toClass());
return true;
}
return false;
}));
application.register(WebSocketFeature.class);
}
private static class WebSocketFeature implements Feature {
@Inject
private InjectionManager injectionManager;
@Inject
private ServerContainer serverContainer;
/**
* {@inheritDoc}
*/
@Override
@SuppressWarnings("unchecked")
public boolean configure(FeatureContext context) {
context.register(new WebSocketBinder());
if (serverContainer == null) {
logger.warn(Messages.get("web.socket.server.unsupported"));
}
for (Class endpointClass : endpointClasses) {
WebSocket webSocket = getAnnotation(WebSocket.class, endpointClass);
if (webSocket == null) continue;
Class<? extends Annotation> scope = getScope(endpointClass);
final Binding binding = Bindings.service(endpointClass).to(endpointClass).in(scope);
ServiceLocators.getProviderContracts(endpointClass).forEach(binding::to);
injectionManager.register(binding);
DefaultServerEndpointConfig endpointConfig = new DefaultServerEndpointConfig(
injectionManager,
endpointClass,
webSocket
);
if (serverContainer != null) {
try {
serverContainer.addEndpoint(endpointConfig);
} catch (DeploymentException e) {
throw new WebSocketException(e);
}
}
if (webSocket.withSockJS()) {
// create resource use modelProcessor
}
}
return true;
}
private Class<? extends Annotation> getScope(final Class<?> clazz) {
Class<? extends Annotation> hk2Scope = RequestScoped.class;
if (clazz.isAnnotationPresent(Singleton.class)) {
hk2Scope = Singleton.class;
} else if (clazz.isAnnotationPresent(PerLookup.class)) {
hk2Scope = PerLookup.class;
}
return hk2Scope;
}
}
}