/*
* JBoss, Home of Professional Open Source
* Copyright 2010, Red Hat, Inc. and/or its affiliates, and individual
* contributors by the @authors tag. See the copyright.txt in the
* distribution for a full listing of individual contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jboss.weld.bean.proxy;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.EmptyStackException;
import org.jboss.weld.contexts.cache.RequestScopedCache;
import org.jboss.weld.contexts.cache.RequestScopedItem;
/**
* A class that holds the interception (and decoration) contexts which are currently in progress.
* <p/>
* An interception context is a set of {@link CombinedInterceptorAndDecoratorStackMethodHandler} references for which interception is currently
* suppressed (so that self-invocation is not possible).
* Such references are added as soon as a CombinedMethodHandler is executed in an interception context that
* does not hold it.
* <p/>
* Classes may create new interception contexts as necessary (e.g. allowing client proxies to create new interception
* contexts in order to make circular references interceptable multiple times).
*
* @author Marius Bogoevici
*/
public class InterceptionDecorationContext {
private static ThreadLocal<Stack> interceptionContexts = new ThreadLocal<Stack>();
public static class Stack implements RequestScopedItem {
private boolean removeWhenEmpty;
private final Deque<CombinedInterceptorAndDecoratorStackMethodHandler> elements;
private final ThreadLocal<Stack> interceptionContexts;
private boolean valid;
private Stack(ThreadLocal<Stack> interceptionContexts) {
this.interceptionContexts = interceptionContexts;
this.elements = new ArrayDeque<CombinedInterceptorAndDecoratorStackMethodHandler>();
/*
* Setting / removing of a thread-local is much more expensive compared to get. Therefore,
* if RequestScopedCache is active we register the thread-local for removal at the end of the
* request. This yields possitive results only if the number of intercepted invocations is large.
* If it is not, the performance characteristics are similar to explicitly removing the thread-local
* once the stack gets empty.
*/
this.removeWhenEmpty = !RequestScopedCache.addItemIfActive(this);
this.valid = true;
}
/**
* Pushes the given context to the stack if the given context is not on top of the stack already.
* If push happens, the caller is responsible for calling {@link #endInterceptorContext()} after the invocation finishes.
* @param context the given context
* @return true if the given context was pushed to the top of the stack, false if the given context was on top already
*/
public boolean startIfNotOnTop(CombinedInterceptorAndDecoratorStackMethodHandler context) {
checkState();
if (elements.isEmpty() || peek() != context) {
push(context);
return true;
}
return false;
}
public void end() {
pop();
}
private void push(CombinedInterceptorAndDecoratorStackMethodHandler item) {
checkState();
elements.addFirst(item);
}
public CombinedInterceptorAndDecoratorStackMethodHandler peek() {
checkState();
return elements.peekFirst();
}
private CombinedInterceptorAndDecoratorStackMethodHandler pop() {
checkState();
CombinedInterceptorAndDecoratorStackMethodHandler top = elements.removeFirst();
removeIfEmpty();
return top;
}
private void checkState() {
if (!valid) {
throw new IllegalStateException("This InterceptionDecorationContext is no longer valid.");
}
}
@Override
public void invalidate() {
/*
* This cached item is being invalidated.
* It does not necessarily mean that the request is being destroyed - it may just be the case that it is being flushed in the middle
* of a request (e.g. for AlterableContext.destroy()).
* Therefore, we cannot remove IDC now but we just set removeWhenEmpty flag and let it remove itself once the stack gets empty.
*/
removeWhenEmpty = true;
removeIfEmpty();
}
private void removeIfEmpty() {
if (removeWhenEmpty && elements.isEmpty()) {
interceptionContexts.remove();
valid = false;
}
}
public int size() {
return elements.size();
}
@Override
public String toString() {
return "Stack [valid=" + valid + ", cached=" + !removeWhenEmpty + ", elements=" + elements + "]";
}
}
private InterceptionDecorationContext() {
}
/**
* Peeks the current top of the stack.
* @return the current top of the stack
* @throws EmptyStackException
*/
public static CombinedInterceptorAndDecoratorStackMethodHandler peek() {
return peek(interceptionContexts.get());
}
/**
* Peeks the current top of the stack or returns null if the stack is empty
* @return the current top of the stack or returns null if the stack is empty
*/
public static CombinedInterceptorAndDecoratorStackMethodHandler peekIfNotEmpty() {
Stack stack = interceptionContexts.get();
if (stack == null) {
return null;
}
return stack.peek();
}
/**
* Indicates whether the stack is empty.
*/
public static boolean empty() {
return empty(interceptionContexts.get());
}
public static void endInterceptorContext() {
pop(interceptionContexts.get());
}
/**
* This is called by client proxies. Calling a method on a client proxy means that we left the interception context of the calling bean. Therefore,
* client proxies call this method to start a new interception context of the called (possibly intercepted) bean. If however there is not interception context
* at the time the proxy is called (meaning the caller is not intercepted), there is no need to create new interception context. This is an optimization as the
* first startInterceptorContext call is expensive.
*
* If this method returns a non-null value, the caller of this method is required to call {@link Stack#end()} on the returned value.
*/
public static Stack startIfNotEmpty() {
Stack stack = getStack();
if (!stack.elements.isEmpty()) {
stack.push(CombinedInterceptorAndDecoratorStackMethodHandler.NULL_INSTANCE);
return stack;
} else {
// if RequestScopedCache is not active, remove now to prevent ThreadLocal leak
stack.removeIfEmpty();
return null;
}
}
/**
* Pushes the given context to the stack if the given context is not on top of the stack already.
* If this method return a non-null value, the caller is responsible for calling {@link #endInterceptorContext()}
* after the invocation finishes.
* @param context the given context
* @return true if the given context was pushed to the top of the stack, false if the given context was on top already
*/
public static Stack startIfNotOnTop(CombinedInterceptorAndDecoratorStackMethodHandler context) {
Stack stack = getStack();
if (stack.startIfNotOnTop(context)) {
return stack;
}
return null;
}
/**
* Gets the current Stack. If the stack is not set, a new empty instance is created and set.
* @return
*/
public static Stack getStack() {
Stack stack = interceptionContexts.get();
if (stack == null) {
stack = new Stack(interceptionContexts);
interceptionContexts.set(stack);
}
return stack;
}
private static CombinedInterceptorAndDecoratorStackMethodHandler pop(Stack stack) {
if (stack == null) {
throw new EmptyStackException();
} else {
return stack.pop();
}
}
private static CombinedInterceptorAndDecoratorStackMethodHandler peek(Stack stack) {
if (stack == null) {
throw new EmptyStackException();
} else {
return stack.peek();
}
}
private static boolean empty(Stack stack) {
if (stack == null) {
return true;
} else {
return stack.elements.isEmpty();
}
}
}