/*
* This file is part of LibrePlan
*
* Copyright (C) 2009-2010 Fundación para o Fomento da Calidade Industrial e
* Desenvolvemento Tecnolóxico de Galicia
* Copyright (C) 2010-2011 Igalia, S.L.
*
* This program 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.zkoss.ganttz.servlets;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.Validate;
/**
* Servlet that allows to register custom responses. It must be declared at
* web.xml having a load-on-startup element.
* @author Óscar González Fernández <ogonzalez@igalia.com>
*/
public class CallbackServlet extends HttpServlet {
private static final String MAPPING = "/callback/";
private static final long CLEANING_PERIOD_MILLIS = 1000 * 60; // one minute
private static Random random = new Random();
private static ConcurrentMap<String, IHandler> handlersCallbacks = new ConcurrentHashMap<>();
private static Timer cleaningTimer = new Timer(true);
public interface IServletRequestHandler {
void handle(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;
}
public enum DisposalMode {
WHEN_NO_LONGER_REFERENCED {
@Override
public IHandler create(IServletRequestHandler handler) {
return new WeakReferencedHandler(handler);
}
},
AFTER_TEN_MINUTES {
@Override
public IHandler create(IServletRequestHandler handler) {
return new BasedOnExpirationTimeHandler(handler, tenMinutesInMillis);
}
};
private static final long tenMinutesInMillis = TimeUnit.MILLISECONDS.convert(10, TimeUnit.MINUTES);
public abstract IHandler create(IServletRequestHandler handler);
}
private interface IHandler {
boolean hasExpired();
IServletRequestHandler getHandler();
}
private static class BasedOnExpirationTimeHandler implements IHandler {
private IServletRequestHandler handler;
private final long creationTime;
private final long expirationTimeMilliseconds;
public BasedOnExpirationTimeHandler(IServletRequestHandler handler, long expirationTimeMilliseconds) {
Validate.notNull(handler);
this.handler = handler;
this.creationTime = System.currentTimeMillis();
this.expirationTimeMilliseconds = expirationTimeMilliseconds;
}
@Override
public IServletRequestHandler getHandler() {
return handler;
}
@Override
public boolean hasExpired() {
return System.currentTimeMillis() - creationTime > expirationTimeMilliseconds;
}
}
private static class WeakReferencedHandler implements IHandler {
private final WeakReference<IServletRequestHandler> handler;
WeakReferencedHandler(IServletRequestHandler handler) {
this.handler = new WeakReference<>(handler);
}
@Override
public boolean hasExpired() {
return handler.get() == null;
}
@Override
public IServletRequestHandler getHandler() {
return handler.get();
}
}
public static String registerAndCreateURLFor(HttpServletRequest request, IServletRequestHandler handler) {
return registerAndCreateURLFor(request, handler, DisposalMode.AFTER_TEN_MINUTES);
}
public static String registerAndCreateURLFor(HttpServletRequest request, IServletRequestHandler handler,
DisposalMode disposalMode) {
return registerAndCreateURLFor(request, handler, true, disposalMode);
}
public static String registerAndCreateURLFor(HttpServletRequest request, IServletRequestHandler handler,
boolean withContextPath) {
return registerAndCreateURLFor(request, handler, withContextPath, DisposalMode.AFTER_TEN_MINUTES);
}
public static String registerAndCreateURLFor(HttpServletRequest request,
IServletRequestHandler handler,
boolean withContextPath,
DisposalMode disposalMode) {
Validate.notNull(disposalMode);
// theoretically could be an infinite loop, could be improved.
String generatedKey;
IHandler toBeRegistered = disposalMode.create(handler);
do {
generatedKey = generateKey();
} while (handlersCallbacks.putIfAbsent(generatedKey, toBeRegistered) != null);
return buildURLFromKey(request, generatedKey, withContextPath);
}
private static synchronized String buildURLFromKey(HttpServletRequest request, String generatedKey,
boolean withContextPath) {
String contextPath = withContextPath ? request.getContextPath() : "";
return contextPath + MAPPING + generatedKey;
}
private static String generateKey() {
return "" + random.nextInt(Integer.MAX_VALUE);
}
private static String getId(String pathInfo) {
if ( pathInfo.startsWith("/") ) {
return pathInfo.substring(1);
}
return pathInfo;
}
private static void cleanExpired() {
remove(findExpired());
}
private static void remove(List<String> expired) {
for (String key : expired) {
handlersCallbacks.remove(key);
}
}
private static List<String> findExpired() {
ArrayList<Entry<String, IHandler>> handlersList = new ArrayList<>(handlersCallbacks.entrySet());
List<String> expired = new ArrayList<>();
for (Entry<String, IHandler> entry : handlersList) {
if ( entry.getValue().hasExpired() ) {
expired.add(entry.getKey());
}
}
return expired;
}
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
scheduleTimer();
}
private void scheduleTimer() {
cleaningTimer.schedule(cleaningTask(), CLEANING_PERIOD_MILLIS, CLEANING_PERIOD_MILLIS);
}
private TimerTask cleaningTask() {
return new TimerTask() {
@Override
public void run() {
cleanExpired();
}
};
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String callbackId = getId(req.getPathInfo());
IServletRequestHandler handler = handlerFor(callbackId);
if ( handler == null ) {
resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
} else {
handler.handle(req, resp);
}
}
private IServletRequestHandler handlerFor(String callbackId) {
IHandler h = handlersCallbacks.get(callbackId);
return h != null ? h.getHandler() : null;
}
}