/*******************************************************************************
* Copyright (c) 2011 Subgraph.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Subgraph - initial API and implementation
******************************************************************************/
package com.subgraph.vega.internal.http.proxy;
import java.util.ArrayList;
import java.util.List;
import org.apache.http.HttpResponse;
import com.subgraph.vega.api.events.IEvent;
import com.subgraph.vega.api.events.IEventHandler;
import com.subgraph.vega.api.http.proxy.HttpInterceptorLevel;
import com.subgraph.vega.api.http.proxy.IHttpInterceptor;
import com.subgraph.vega.api.http.proxy.IHttpInterceptorEventHandler;
import com.subgraph.vega.api.http.proxy.IProxyTransaction;
import com.subgraph.vega.api.http.proxy.IProxyTransaction.TransactionDirection;
import com.subgraph.vega.api.model.IModel;
import com.subgraph.vega.api.model.IWorkspace;
import com.subgraph.vega.api.model.WorkspaceCloseEvent;
import com.subgraph.vega.api.model.WorkspaceOpenEvent;
import com.subgraph.vega.api.model.WorkspaceResetEvent;
import com.subgraph.vega.api.model.conditions.ConditionSetChanged;
import com.subgraph.vega.api.model.conditions.IHttpConditionManager;
import com.subgraph.vega.api.model.conditions.IHttpConditionSet;
import com.subgraph.vega.internal.http.proxy.ProxyTransaction;
public class HttpInterceptor implements IHttpInterceptor {
private static final String propertyInterceptorLevelRequest = "vega.preferences.proxy.interceptor.level.request";
private static final String propertyInterceptorLevelResponse = "vega.preferences.proxy.interceptor.level.response";
private final Object interceptorLock = new Object(); /**< Lock for contents of object */
private final List<IHttpInterceptorEventHandler> eventHandlerList;
private HttpInterceptorLevel interceptorLevelRequest = HttpInterceptorLevel.DISABLED;
private HttpInterceptorLevel interceptorLevelResponse = HttpInterceptorLevel.DISABLED;
private IHttpConditionSet breakpointSetRequest;
private IHttpConditionSet breakpointSetResponse;
private final ArrayList<ProxyTransaction> transactionQueue = new ArrayList<ProxyTransaction>(); /**< Queue of intercepted transactions pending processing */
private IWorkspace currentWorkspace;
private boolean isEnabled = true;
HttpInterceptor(IModel model) {
eventHandlerList = new ArrayList<IHttpInterceptorEventHandler>();
currentWorkspace = model.addWorkspaceListener(new IEventHandler() {
@Override
public void handleEvent(IEvent event) {
if(event instanceof WorkspaceOpenEvent) {
handleWorkspaceOpen((WorkspaceOpenEvent) event);
} else if(event instanceof WorkspaceCloseEvent) {
handleWorkspaceClose((WorkspaceCloseEvent) event);
} else if (event instanceof WorkspaceResetEvent) {
handleWorkspaceReset((WorkspaceResetEvent) event);
}
}
});
loadInterceptorLevelRequest();
loadInterceptorLevelResponse();
breakpointSetRequest = createConditionSet(model, true);
breakpointSetResponse = createConditionSet(model, false);
}
private void handleWorkspaceOpen(WorkspaceOpenEvent event) {
currentWorkspace = event.getWorkspace();
}
private void handleWorkspaceClose(WorkspaceCloseEvent event) {
currentWorkspace = null;
}
private void handleWorkspaceReset(WorkspaceResetEvent event) {
currentWorkspace = event.getWorkspace();
loadInterceptorLevelRequest();
loadInterceptorLevelResponse();
}
private void loadInterceptorLevelRequest() {
interceptorLevelRequest = HttpInterceptorLevel.fromValue(currentWorkspace.getIntegerProperty(propertyInterceptorLevelRequest));
if (interceptorLevelRequest == null) {
interceptorLevelRequest = HttpInterceptorLevel.DISABLED;
}
}
private void loadInterceptorLevelResponse() {
interceptorLevelResponse = HttpInterceptorLevel.fromValue(currentWorkspace.getIntegerProperty(propertyInterceptorLevelResponse));
if (interceptorLevelResponse == null) {
interceptorLevelResponse = HttpInterceptorLevel.DISABLED;
}
}
private IHttpConditionSet createConditionSet(IModel model, boolean isRequestSet) {
final String name = (isRequestSet) ? (IHttpConditionManager.CONDITION_SET_BREAKPOINTS_REQUEST) : (IHttpConditionManager.CONDITION_SET_BREAKPOINTS_RESPONSE);
return model.addConditionSetTracker(name, createConditionSetChangedHandler(isRequestSet));
}
private IEventHandler createConditionSetChangedHandler(final boolean isRequestSet) {
return new IEventHandler() {
@Override
public void handleEvent(IEvent event) {
if(event instanceof ConditionSetChanged)
onConditionSetChanged((ConditionSetChanged) event, isRequestSet);
}
};
}
private void onConditionSetChanged(ConditionSetChanged event, boolean isRequestSet) {
synchronized(interceptorLock) {
final TransactionDirection direction;
if(isRequestSet) {
breakpointSetRequest = event.getConditionSet();
direction = TransactionDirection.DIRECTION_REQUEST;
} else {
breakpointSetResponse = event.getConditionSet();
direction = TransactionDirection.DIRECTION_RESPONSE;
}
releaseOnChange(direction);
}
}
private boolean intercept(ProxyTransaction transaction) {
if (transaction.hasResponse() == false)
return interceptByLevelAndBreakpointSet(transaction, interceptorLevelRequest, breakpointSetRequest);
else
return interceptByLevelAndBreakpointSet(transaction, interceptorLevelResponse, breakpointSetResponse);
}
private boolean interceptByLevelAndBreakpointSet(ProxyTransaction transaction, HttpInterceptorLevel level, IHttpConditionSet breakpointSet) {
switch(level) {
case ENABLED_ALL:
return true;
case DISABLED:
return false;
case ENABLED_BREAKPOINTS:
return interceptOnBreakpointSet(breakpointSet, transaction);
}
return false;
}
private boolean interceptOnBreakpointSet(IHttpConditionSet breakpointSet, ProxyTransaction transaction) {
final HttpResponse response = (transaction.hasResponse()) ? (transaction.getResponse().getRawResponse()) : (null);
synchronized(interceptorLock) {
return (breakpointSet == null) ? (false) : (breakpointSet.matchesAny(transaction.getRequest(), response));
}
}
/**
* @return True if the transaction was intercepted and added to the queue for processing, false if it can immediately
* be handled.
*/
public boolean handleTransaction(ProxyTransaction transaction) {
synchronized(interceptorLock) {
if (isEnabled == true && eventHandlerList.size() != 0 && intercept(transaction) != false) {
transaction.setPending(this);
transactionQueue.add(transaction);
int idx = transactionQueue.size() - 1;
for (IHttpInterceptorEventHandler handler: eventHandlerList) {
handler.notifyQueue(transaction, idx);
}
return true;
}
return false;
}
}
@Override
public void setEnabled(boolean enabled) {
synchronized(interceptorLock) {
if (isEnabled != enabled) {
isEnabled = enabled;
if (isEnabled == false) {
forwardAll();
}
}
}
}
@Override
public boolean isEnabled() {
synchronized(interceptorLock) {
return isEnabled;
}
}
@Override
public void addEventHandler(IHttpInterceptorEventHandler eventHandler) {
synchronized(interceptorLock) {
eventHandlerList.add(eventHandler);
}
}
@Override
public void removeEventHandler(IHttpInterceptorEventHandler eventHandler) {
synchronized(interceptorLock) {
eventHandlerList.remove(eventHandler);
}
}
@Override
public void setInterceptLevel(TransactionDirection direction, HttpInterceptorLevel level) {
synchronized(interceptorLock) {
if (direction == TransactionDirection.DIRECTION_REQUEST) {
interceptorLevelRequest = level;
currentWorkspace.setIntegerProperty(propertyInterceptorLevelRequest, level.getSerializeValue());
} else {
interceptorLevelResponse = level;
currentWorkspace.setIntegerProperty(propertyInterceptorLevelResponse, level.getSerializeValue());
}
releaseOnChange(direction);
}
}
@Override
public HttpInterceptorLevel getInterceptLevel( TransactionDirection direction) {
synchronized(interceptorLock) {
if (direction == TransactionDirection.DIRECTION_REQUEST) {
return interceptorLevelRequest;
} else {
return interceptorLevelResponse;
}
}
}
@Override
public int transactionQueueSize() {
synchronized(interceptorLock) {
return transactionQueue.size();
}
}
@Override
public IProxyTransaction[] getTransactions() {
synchronized(interceptorLock) {
return transactionQueue.toArray(new IProxyTransaction[transactionQueue.size()]);
}
}
@Override
public IProxyTransaction transactionQueueGet(int idx) {
synchronized(interceptorLock) {
if (transactionQueue.size() <= idx) {
return null;
}
return transactionQueue.get(idx);
}
}
/**
* Notification that a proxy transaction queued by this interceptor was handled. The transaction is removed from the
* transaction queue.
*
* @param transaction Transaction
*/
public void notifyHandled(ProxyTransaction transaction) {
synchronized(interceptorLock) {
final int idx = transactionQueue.indexOf(transaction);
transactionQueue.remove(idx);
if (transactionQueue.size() == 0) {
for (IHttpInterceptorEventHandler handler: eventHandlerList) {
handler.notifyEmpty();
}
} else {
for (IHttpInterceptorEventHandler handler: eventHandlerList) {
handler.notifyRemove(idx);
}
}
}
}
private IHttpConditionSet getBreakpointSet(TransactionDirection direction) {
synchronized(interceptorLock) {
if (direction == TransactionDirection.DIRECTION_REQUEST) {
return breakpointSetRequest;
} else {
return breakpointSetResponse;
}
}
}
/**
* Release transactions that no longer match interception criteria following a configuration change. Must be invoked
* with interceptorLock synchronized.
*/
private void releaseOnChange(TransactionDirection direction) {
final HttpInterceptorLevel level = getInterceptLevel(direction);
if (level != HttpInterceptorLevel.ENABLED_ALL) {
if (level != HttpInterceptorLevel.DISABLED) {
final IHttpConditionSet breakpointSet = getBreakpointSet(direction);
for (int idx = 0; idx < transactionQueue.size(); idx++) {
ProxyTransaction transaction = transactionQueue.get(idx);
if (transaction.hasResponse() == (direction == TransactionDirection.DIRECTION_RESPONSE)) {
if (interceptOnBreakpointSet(breakpointSet, transaction) == false) {
transaction.doForward();
}
}
}
} else {
for (int idx = 0; idx < transactionQueue.size(); idx++) {
ProxyTransaction transaction = transactionQueue.get(idx);
if (transaction.hasResponse() == (direction == TransactionDirection.DIRECTION_RESPONSE)) {
transaction.doForward();
}
}
}
}
}
/**
* Forward all pending transactions. Must be invoked with interceptorLock synchronized.
*/
private void forwardAll() {
for (int idx = 0; idx < transactionQueue.size(); idx++) {
ProxyTransaction transaction = transactionQueue.get(idx);
transaction.doDrop();
}
}
}