View Javadoc
1   /*
2    * Copyright (C) 2007-2012 Argeo GmbH
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *         http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.argeo.slc.core.execution;
17  
18  import org.apache.commons.logging.Log;
19  import org.apache.commons.logging.LogFactory;
20  import org.argeo.slc.SlcException;
21  import org.argeo.slc.UnsupportedException;
22  import org.argeo.slc.execution.ExecutionContext;
23  import org.argeo.slc.execution.ExecutionFlow;
24  import org.argeo.slc.execution.ExecutionSpec;
25  import org.argeo.slc.execution.ExecutionStack;
26  import org.springframework.beans.factory.ObjectFactory;
27  import org.springframework.beans.factory.config.Scope;
28  
29  /**
30   * When Spring beans are instantiated with this scope, the same instance is
31   * reused across an execution.
32   */
33  public class ExecutionScope implements Scope {
34  	private final static Log log = LogFactory.getLog(ExecutionScope.class);
35  
36  	private final ThreadLocal<ExecutionStack> executionStack = new ThreadLocal<ExecutionStack>();
37  	public final ThreadLocal<String> executionStackBeanName = new ThreadLocal<String>();
38  
39  	private final ThreadLocal<ExecutionContext> executionContext = new ThreadLocal<ExecutionContext>();
40  	private final ThreadLocal<String> executionContextBeanName = new ThreadLocal<String>();
41  
42  	public Object get(String name, ObjectFactory<?> objectFactory) {
43  		if (log.isTraceEnabled())
44  			log.debug("Get execution scoped bean " + name);
45  
46  		// shortcuts
47  		if (executionStackBeanName.get() != null
48  				&& name.equals(executionStackBeanName.get())) {
49  			return executionStack.get();
50  		}
51  
52  		if (executionContextBeanName.get() != null
53  				&& name.equals(executionContextBeanName.get())) {
54  			return executionContext.get();
55  		}
56  
57  		// execution context must be defined first
58  		if (executionContext.get() == null) {
59  			Object obj = objectFactory.getObject();
60  			if (obj instanceof ExecutionContext) {
61  				return dealWithSpecialScopedObject(name, executionContext,
62  						executionContextBeanName, (ExecutionContext) obj);
63  			} else {
64  				// TODO: use execution context wrapper
65  				throw new SlcException("No execution context has been defined.");
66  			}
67  		}
68  
69  		// for other scoped objects, an executions stack must be available
70  		if (executionStack.get() == null) {
71  			Object obj = objectFactory.getObject();
72  			if (obj instanceof ExecutionStack) {
73  				return dealWithSpecialScopedObject(name, executionStack,
74  						executionStackBeanName, (ExecutionStack) obj);
75  			} else {
76  				throw new SlcException("No execution stack has been defined.");
77  			}
78  		}
79  
80  		// see if the execution stack already knows the object
81  		Object obj = executionStack.get().findScopedObject(name);
82  		if (obj == null) {
83  			obj = objectFactory.getObject();
84  			if (obj instanceof ExecutionContext)
85  				throw new SlcException(
86  						"Only one execution context can be defined per thread");
87  			if (obj instanceof ExecutionStack)
88  				throw new SlcException(
89  						"Only one execution stack can be defined per thread");
90  
91  			checkForbiddenClasses(obj);
92  
93  			executionStack.get().addScopedObject(name, obj);
94  		}
95  		return obj;
96  
97  	}
98  
99  	protected <T> T dealWithSpecialScopedObject(String name,
100 			ThreadLocal<T> threadLocal,
101 			ThreadLocal<String> threadLocalBeanName, T newObj) {
102 
103 		T obj = threadLocal.get();
104 		if (obj == null) {
105 			obj = newObj;
106 			threadLocal.set(obj);
107 			threadLocalBeanName.set(name);
108 			if (log.isTraceEnabled()) {
109 				log.debug(obj.getClass() + " instantiated. (beanName=" + name
110 						+ ")");
111 			}
112 			return obj;
113 		} else {
114 			throw new SlcException("Only one scoped " + obj.getClass()
115 					+ " can be defined per thread");
116 		}
117 
118 	}
119 
120 	protected void checkForbiddenClasses(Object obj) {
121 		Class<?> clss = obj.getClass();
122 		if (ExecutionFlow.class.isAssignableFrom(clss)
123 				|| ExecutionSpec.class.isAssignableFrom(clss)) {
124 			throw new UnsupportedException("Execution scoped object", clss);
125 		}
126 	}
127 
128 	public String getConversationId() {
129 		// TODO: is it the most relevant?
130 		return executionContext.get().getUuid();
131 	}
132 
133 	public void registerDestructionCallback(String name, Runnable callback) {
134 		if (Thread.currentThread() instanceof ExecutionThread) {
135 			ExecutionThread executionThread = (ExecutionThread) Thread
136 					.currentThread();
137 			executionThread.registerDestructionCallback(name, callback);
138 		}
139 	}
140 
141 	public Object remove(String name) {
142 		if (log.isDebugEnabled())
143 			log.debug("Remove object " + name);
144 		throw new UnsupportedOperationException();
145 	}
146 
147 	public Object resolveContextualObject(String key) {
148 		return executionContext.get().getVariable(key);
149 	}
150 
151 }