package net.contextfw.web.application.scope; import java.util.ConcurrentModificationException; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Timer; import java.util.TimerTask; import java.util.UUID; import javax.servlet.http.HttpServletRequest; import net.contextfw.web.application.WebApplication; import net.contextfw.web.application.WebApplicationException; import net.contextfw.web.application.PageHandle; import net.contextfw.web.application.configuration.Configuration; import net.contextfw.web.application.configuration.SettableProperty; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.inject.Inject; import com.google.inject.Singleton; @Singleton public class DefaultWebApplicationStorage implements WebApplicationStorage { private static final int MAX_LENGTH = 16; private Logger logger = LoggerFactory.getLogger(DefaultWebApplicationStorage.class); private final Map<PageHandle, Holder> pages = new HashMap<PageHandle, Holder>(); public static final SettableProperty<Boolean> PROXIED = Configuration.createProperty(Boolean.class, DefaultWebApplicationStorage.class.getName() + ".proxied"); private final boolean proxied; private static final class Holder { private long validThrough; private final String remoteAddr; private final WebApplication application; private final Map<String, Object> largeObjects = new HashMap<String, Object>(); private Holder(WebApplication application, String remoteAddr, long validThrough) { this.application = application; this.remoteAddr = remoteAddr; this.validThrough = validThrough; } } @Inject public DefaultWebApplicationStorage(Configuration configuration) { this.proxied = configuration.getOrElse(PROXIED, false); Timer timer = new Timer(true); logger.info("Starting scheduled removal for expired web applications"); timer.schedule(new TimerTask() { public void run() { removeExpiredPages(); } }, configuration.get(Configuration.REMOVAL_SCHEDULE_PERIOD), configuration.get(Configuration.REMOVAL_SCHEDULE_PERIOD)); } @Override public void initialize(WebApplication application, HttpServletRequest request, long validThrough, ScopedWebApplicationExecution execution) { PageHandle handle = createHandle(); application.setHandle(handle); Holder holder = new Holder(application, getRemoteAddr(request), validThrough); synchronized(this) { pages.put(handle, holder); } synchronized (holder) { execution.execute(holder.application); } } @Override public void update(PageHandle handle, HttpServletRequest request, long validThrough, ScopedWebApplicationExecution execution) { Holder holder = getHolder(handle, request); if (holder != null) { synchronized (holder) { holder.validThrough = validThrough; execution.execute(holder.application); } } else { execution.execute(null); } } @Override public void refresh(PageHandle handle, HttpServletRequest request, long validThrough) { Holder holder = getHolder(handle, request); if (holder != null) { holder.validThrough = validThrough; } } private Holder getHolder(PageHandle handle, HttpServletRequest request) { Holder holder; synchronized (this) { holder = pages.get(handle); } String remoteAddr = getRemoteAddr(request); long now = System.currentTimeMillis(); if (holder != null && holder.remoteAddr.equals(remoteAddr) && holder.validThrough >= now) { return holder; } else { return null; } } @Override public synchronized void remove(PageHandle handle, HttpServletRequest request) { Holder holder = getHolder(handle, request); if (holder != null) { pages.remove(handle); pageRemoved(handle, pages.size(), getRemoteAddr(request)); } } protected String getRemoteAddr(HttpServletRequest request) { if (proxied) { String proxy = StringUtils.trimToEmpty(request.getHeader("X-Forwarded-For")); int length = proxy.length(); if (length > MAX_LENGTH) { return proxy.substring(length - MAX_LENGTH, length); } else { return proxy; } } else { return request.getRemoteAddr(); } } protected void pageRemoved(PageHandle handle, int pageCount, String remoteAddr) { } protected void pageExpired(PageHandle handle, int pageCount, String remoteAddr) { } protected void throttle() { } private synchronized void removeExpiredPages() { long now = System.currentTimeMillis(); try { Iterator<Entry<PageHandle, Holder>> iterator = pages.entrySet().iterator(); while (iterator.hasNext()) { Entry<PageHandle, Holder> entry = iterator.next(); if (entry.getValue().validThrough < now) { pageExpired(entry.getKey(), pages.size(), entry.getValue().remoteAddr); iterator.remove(); } } } catch (ConcurrentModificationException cme) { // Swallowing this exception, because // it is not really a big deal. } } protected PageHandle createHandle() { PageHandle handle; //do { handle = new PageHandle(UUID.randomUUID().toString()); //} while (pages.containsKey(handle)); return handle; } @Override public void execute(PageHandle handle, ScopedWebApplicationExecution execution) { Holder holder; synchronized (this) { holder = pages.get(handle); } if (holder != null) { synchronized (holder) { execution.execute(holder.application); } } else { execution.execute(null); } } @Override public void storeLarge(PageHandle handle, String key, Object obj) { if (handle == null) { throw new IllegalArgumentException("Handle cannot be null"); } else if (StringUtils.isBlank(key)) { throw new IllegalArgumentException("Key cannot be null!"); } synchronized (this) { Holder holder = pages.get(handle); if (holder == null) { throw new WebApplicationException("Page scope does not exist!"); } if (obj == null) { holder.largeObjects.remove(key); } else { holder.largeObjects.put(key, obj); } } } @SuppressWarnings("unchecked") @Override public <T> T loadLarge(PageHandle handle, String key, Class<T> type) { if (handle == null) { throw new IllegalArgumentException("Handle cannot be null"); } else if (StringUtils.isBlank(key)) { throw new IllegalArgumentException("Key cannot be null!"); } synchronized (this) { Holder holder = pages.get(handle); if (holder == null) { throw new WebApplicationException("Page scope does not exist!"); } return (T) holder.largeObjects.get(key); } } }