/**
* diqube: Distributed Query Base.
*
* Copyright (C) 2015 Bastian Gloeckle
*
* This file is part of diqube.
*
* diqube is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.diqube.ui;
import java.io.IOException;
import java.util.function.Function;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.thrift.TException;
import org.apache.thrift.TMultiplexedProcessor;
import org.apache.thrift.TProcessor;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.protocol.TProtocolFactory;
import org.apache.thrift.server.TServlet;
import org.diqube.remote.query.IdentityCallbackServiceConstants;
import org.diqube.remote.query.KeepAliveServiceConstants;
import org.diqube.remote.query.QueryResultServiceConstants;
import org.diqube.remote.query.thrift.IdentityCallbackService;
import org.diqube.remote.query.thrift.KeepAliveService;
import org.diqube.remote.query.thrift.QueryResultService;
import org.diqube.ticket.IdentityCallbackHandler;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
/**
* Endpoint servlet for calls on the Thrift services this webapp provides. These are usually calls from cluster nodes
* which have e.g. new result data available.
*
* @author Bastian Gloeckle
*/
public class ThriftServlet extends TServlet {
private static final long serialVersionUID = 1L;
/** make sure this matches the web.xml. */
public static final String URL_PATTERN = "/t";
private static final short NUMBER_OF_PROCESSORS = 3;
private static ThreadLocal<ApplicationContext> validAppContext = new ThreadLocal<>();
private static ThreadLocal<Integer> initializedDelegates = new ThreadLocal<>();
private short numberOfProcessorsInitialized = 0;
private Object numberOfProcessorsInitializedSync = new Object();
public ThriftServlet() {
super(createProcessor(), createProtocolFactory());
}
private static TProcessor createProcessor() {
TMultiplexedProcessor res = new TMultiplexedProcessor();
res.registerProcessor(QueryResultServiceConstants.SERVICE_NAME,
new LazyBindingProcessorProvider<>(QueryResultServiceHandler.class,
handler -> new QueryResultService.Processor<QueryResultService.Iface>(handler)));
res.registerProcessor(KeepAliveServiceConstants.SERVICE_NAME, new LazyBindingProcessorProvider<>(
KeepAliveServiceHandler.class, handler -> new KeepAliveService.Processor<KeepAliveService.Iface>(handler)));
res.registerProcessor(IdentityCallbackServiceConstants.SERVICE_NAME,
new LazyBindingProcessorProvider<>(IdentityCallbackHandler.class,
handler -> new IdentityCallbackService.Processor<IdentityCallbackService.Iface>(handler)));
// when adding new processors, update NUMBER_OF_PROCESSORS
return res;
}
private static TProtocolFactory createProtocolFactory() {
return new TCompactProtocol.Factory();
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
if (numberOfProcessorsInitialized == NUMBER_OF_PROCESSORS) {
super.doPost(request, response);
return;
}
validAppContext.set(WebApplicationContextUtils.getRequiredWebApplicationContext(request.getServletContext()));
try {
super.doPost(request, response);
} finally {
validAppContext.remove();
if (initializedDelegates.get() != null) {
synchronized (numberOfProcessorsInitializedSync) {
numberOfProcessorsInitialized += initializedDelegates.get();
}
initializedDelegates.remove();
}
}
}
/**
* A Processor which will lazily create a delegate processor that is based on a handler object that is available in
* the bean context.
*
* As all initialization methods of the ThriftServlet need to be static (TServelt does not provide a very nice
* interface unfortuantely), we cannot fetch the handlers for the services from the bean context when creating the
* servlet itself. Instead, we install instances of this class as processors. These will then, when first called and
* when provided with the current bean context in {@link ThriftServlet#validAppContext}, fetch the handler objects
* from the bean context and initialize the delegate processors. In addition to that, after initializing a delegate
* processor, this class will set {@link ThriftServlet#initializedDelegates} - using that the enclosing servlet class
* can check if all delegate processors have already been created - and not set the
* {@link ThriftServlet#validAppContext} {@link ThreadLocal} for future calls anymore, to save some sync-time.
*/
private static class LazyBindingProcessorProvider<T> implements TProcessor {
private Class<? extends T> handlerClass;
private Function<T, TProcessor> processorFactory;
private volatile TProcessor delegate = null;
private LazyBindingProcessorProvider(Class<? extends T> handlerClass, Function<T, TProcessor> processorFactory) {
this.handlerClass = handlerClass;
this.processorFactory = processorFactory;
}
@Override
public boolean process(TProtocol in, TProtocol out) throws TException {
if (delegate != null)
return delegate.process(in, out);
synchronized (this) {
if (delegate == null) {
T handler = validAppContext.get().getBean(handlerClass);
delegate = processorFactory.apply(handler);
initializedDelegates.set(1);
}
}
return delegate.process(in, out);
}
}
}