001: /**
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */package org.apache.openejb.core.stateless;
017:
018: import java.lang.reflect.Method;
019: import java.rmi.RemoteException;
020: import java.util.ArrayList;
021: import java.util.HashMap;
022: import java.util.List;
023: import java.util.Map;
024:
025: import javax.ejb.SessionBean;
026: import javax.ejb.SessionContext;
027: import javax.naming.Context;
028: import javax.naming.NamingException;
029: import javax.transaction.TransactionManager;
030: import javax.xml.ws.WebServiceContext;
031:
032: import org.apache.openejb.Injection;
033: import org.apache.openejb.OpenEJBException;
034: import org.apache.openejb.SystemException;
035: import org.apache.openejb.core.BaseContext;
036: import org.apache.openejb.core.CoreDeploymentInfo;
037: import org.apache.openejb.core.Operation;
038: import org.apache.openejb.core.ThreadContext;
039: import org.apache.openejb.core.interceptor.InterceptorData;
040: import org.apache.openejb.core.interceptor.InterceptorStack;
041: import org.apache.openejb.spi.SecurityService;
042: import org.apache.openejb.util.LinkedListStack;
043: import org.apache.openejb.util.LogCategory;
044: import org.apache.openejb.util.Logger;
045: import org.apache.openejb.util.SafeToolkit;
046: import org.apache.openejb.util.Stack;
047: import org.apache.xbean.recipe.ConstructionException;
048: import org.apache.xbean.recipe.ObjectRecipe;
049: import org.apache.xbean.recipe.Option;
050: import org.apache.xbean.recipe.StaticRecipe;
051:
052: public class StatelessInstanceManager {
053: private static final Logger logger = Logger.getInstance(
054: LogCategory.OPENEJB, "org.apache.openejb.util.resources");
055:
056: protected int poolLimit = 0;
057: protected int beanCount = 0;
058: protected boolean strictPooling = false;
059:
060: protected PoolQueue poolQueue = null;
061:
062: protected final SafeToolkit toolkit = SafeToolkit
063: .getToolkit("StatefulInstanceManager");
064: private TransactionManager transactionManager;
065: private SecurityService securityService;
066:
067: public StatelessInstanceManager(
068: TransactionManager transactionManager,
069: SecurityService securityService, int timeout, int poolSize,
070: boolean strictPooling) {
071: this .transactionManager = transactionManager;
072: this .securityService = securityService;
073: this .poolLimit = poolSize;
074: this .strictPooling = strictPooling;
075:
076: if (strictPooling && poolSize < 1) {
077: throw new IllegalArgumentException(
078: "Cannot use strict pooling with a pool size less than one. Strict pooling blocks threads till an instance in the pool is available. Please increase the pool size or set strict pooling to false");
079: }
080:
081: if (this .strictPooling) {
082: poolQueue = new PoolQueue(timeout);
083: }
084: }
085:
086: /**
087: * Removes an instance from the pool and returns it for use
088: * by the container in business methods.
089: *
090: * If the pool is at it's limit the StrictPooling flag will
091: * cause this thread to wait.
092: *
093: * If StrictPooling is not enabled this method will create a
094: * new stateless bean instance performing all required injection
095: * and callbacks before returning it in a method ready state.
096: *
097: * @param callContext
098: * @return
099: * @throws OpenEJBException
100: */
101: public Object getInstance(ThreadContext callContext)
102: throws OpenEJBException {
103: CoreDeploymentInfo deploymentInfo = callContext
104: .getDeploymentInfo();
105: Data data = (Data) deploymentInfo.getContainerData();
106: Stack pool = data.getPool();
107: Object bean = pool.pop();
108:
109: while (strictPooling && bean == null
110: && pool.size() >= poolLimit) {
111: poolQueue.waitForAvailableInstance();
112: bean = pool.pop();
113: }
114:
115: if (bean == null) {
116:
117: Class beanClass = deploymentInfo.getBeanClass();
118: ObjectRecipe objectRecipe = new ObjectRecipe(beanClass);
119: objectRecipe.allow(Option.FIELD_INJECTION);
120: objectRecipe.allow(Option.PRIVATE_PROPERTIES);
121: objectRecipe.allow(Option.IGNORE_MISSING_PROPERTIES);
122:
123: Operation originalOperation = callContext
124: .getCurrentOperation();
125: BaseContext.State[] originalAllowedStates = callContext
126: .getCurrentAllowedStates();
127:
128: try {
129: Context ctx = deploymentInfo.getJndiEnc();
130: SessionContext sessionContext;
131: // This needs to be synchronized as this code is multi-threaded.
132: // In between the lookup and the bind a bind may take place in another Thread.
133: // This is a fix for GERONIMO-3444
134: synchronized (this ) {
135: try {
136: sessionContext = (SessionContext) ctx
137: .lookup("java:comp/EJBContext");
138: } catch (NamingException e1) {
139: sessionContext = createSessionContext();
140: // TODO: This should work
141: ctx
142: .bind("java:comp/EJBContext",
143: sessionContext);
144: }
145: }
146: if (javax.ejb.SessionBean.class
147: .isAssignableFrom(beanClass)
148: || hasSetSessionContext(beanClass)) {
149: callContext
150: .setCurrentOperation(Operation.INJECTION);
151: callContext
152: .setCurrentAllowedStates(StatelessContext
153: .getStates());
154: objectRecipe.setProperty("sessionContext",
155: new StaticRecipe(sessionContext));
156: }
157:
158: WebServiceContext wsContext;
159: // This is a fix for GERONIMO-3444
160: synchronized (this ) {
161: try {
162: wsContext = (WebServiceContext) ctx
163: .lookup("java:comp/WebServiceContext");
164: } catch (NamingException e) {
165: wsContext = new EjbWsContext(sessionContext);
166: ctx.bind("java:comp/WebServiceContext",
167: wsContext);
168: }
169: }
170:
171: fillInjectionProperties(objectRecipe, beanClass,
172: deploymentInfo, ctx);
173:
174: bean = objectRecipe.create(beanClass.getClassLoader());
175: Map unsetProperties = objectRecipe.getUnsetProperties();
176: if (unsetProperties.size() > 0) {
177: for (Object property : unsetProperties.keySet()) {
178: logger.warning("Injection: No such property '"
179: + property + "' in class "
180: + beanClass.getName());
181: }
182: }
183:
184: HashMap<String, Object> interceptorInstances = new HashMap<String, Object>();
185: for (InterceptorData interceptorData : deploymentInfo
186: .getAllInterceptors()) {
187: if (interceptorData.getInterceptorClass().equals(
188: beanClass))
189: continue;
190:
191: Class clazz = interceptorData.getInterceptorClass();
192: ObjectRecipe interceptorRecipe = new ObjectRecipe(
193: clazz);
194: interceptorRecipe.allow(Option.FIELD_INJECTION);
195: interceptorRecipe.allow(Option.PRIVATE_PROPERTIES);
196: interceptorRecipe
197: .allow(Option.IGNORE_MISSING_PROPERTIES);
198:
199: fillInjectionProperties(interceptorRecipe, clazz,
200: deploymentInfo, ctx);
201:
202: try {
203: Object interceptorInstance = interceptorRecipe
204: .create(clazz.getClassLoader());
205: interceptorInstances.put(clazz.getName(),
206: interceptorInstance);
207: } catch (ConstructionException e) {
208: throw new Exception(
209: "Failed to create interceptor: "
210: + clazz.getName(), e);
211: }
212: }
213:
214: interceptorInstances.put(beanClass.getName(), bean);
215:
216: try {
217: callContext
218: .setCurrentOperation(Operation.POST_CONSTRUCT);
219: callContext
220: .setCurrentAllowedStates(StatelessContext
221: .getStates());
222:
223: List<InterceptorData> callbackInterceptors = deploymentInfo
224: .getCallbackInterceptors();
225: InterceptorStack interceptorStack = new InterceptorStack(
226: bean, null, Operation.POST_CONSTRUCT,
227: callbackInterceptors, interceptorInstances);
228: interceptorStack.invoke();
229: } catch (Exception e) {
230: throw e;
231: }
232:
233: try {
234: if (bean instanceof SessionBean) {
235: callContext
236: .setCurrentOperation(Operation.CREATE);
237: callContext
238: .setCurrentAllowedStates(StatelessContext
239: .getStates());
240: Method create = deploymentInfo
241: .getCreateMethod();
242: InterceptorStack interceptorStack = new InterceptorStack(
243: bean, create, Operation.CREATE,
244: new ArrayList<InterceptorData>(),
245: new HashMap());
246: interceptorStack.invoke();
247: }
248: } catch (Exception e) {
249: throw e;
250: }
251:
252: bean = new Instance(bean, interceptorInstances);
253: } catch (Throwable e) {
254: if (e instanceof java.lang.reflect.InvocationTargetException) {
255: e = ((java.lang.reflect.InvocationTargetException) e)
256: .getTargetException();
257: }
258: String t = "The bean instance " + bean
259: + " threw a system exception:" + e;
260: logger.error(t, e);
261: throw new org.apache.openejb.ApplicationException(
262: new RemoteException(
263: "Cannot obtain a free instance.", e));
264: } finally {
265: callContext.setCurrentOperation(originalOperation);
266: callContext
267: .setCurrentAllowedStates(originalAllowedStates);
268: }
269: }
270: return bean;
271: }
272:
273: private static void fillInjectionProperties(
274: ObjectRecipe objectRecipe, Class clazz,
275: CoreDeploymentInfo deploymentInfo, Context context) {
276: for (Injection injection : deploymentInfo.getInjections()) {
277: if (!injection.getTarget().isAssignableFrom(clazz))
278: continue;
279: try {
280: String jndiName = injection.getJndiName();
281: Object object = context.lookup("java:comp/env/"
282: + jndiName);
283: if (object instanceof String) {
284: String string = (String) object;
285: // Pass it in raw so it could be potentially converted to
286: // another data type by an xbean-reflect property editor
287: objectRecipe.setProperty(injection.getTarget()
288: .getName()
289: + "/" + injection.getName(), string);
290: } else {
291: objectRecipe.setProperty(injection.getTarget()
292: .getName()
293: + "/" + injection.getName(),
294: new StaticRecipe(object));
295: }
296: } catch (NamingException e) {
297: logger
298: .warning("Injection data not found in enc: jndiName='"
299: + injection.getJndiName()
300: + "', target="
301: + injection.getTarget()
302: + "/" + injection.getName());
303: }
304: }
305: }
306:
307: private boolean hasSetSessionContext(Class beanClass) {
308: try {
309: beanClass.getMethod("setSessionContext",
310: SessionContext.class);
311: return true;
312: } catch (NoSuchMethodException e) {
313: return false;
314: }
315: }
316:
317: private SessionContext createSessionContext() {
318: return new StatelessContext(transactionManager, securityService);
319: }
320:
321: /**
322: * All instances are removed from the pool in getInstance(...). They are only
323: * returned by the StatelessContainer via this method under two circumstances.
324: *
325: * 1. The business method returns normally
326: * 2. The business method throws an application exception
327: *
328: * Instances are not returned to the pool if the business method threw a system
329: * exception.
330: *
331: * @param callContext
332: * @param bean
333: * @throws OpenEJBException
334: */
335: public void poolInstance(ThreadContext callContext, Object bean)
336: throws OpenEJBException {
337: if (bean == null) {
338: throw new SystemException("Invalid arguments");
339: }
340:
341: CoreDeploymentInfo deploymentInfo = callContext
342: .getDeploymentInfo();
343: Data data = (Data) deploymentInfo.getContainerData();
344: Stack pool = data.getPool();
345:
346: if (strictPooling) {
347: pool.push(bean);
348: poolQueue.notifyWaitingThreads();
349: } else {
350: if (pool.size() >= poolLimit) {
351: freeInstance(callContext, (Instance) bean);
352: } else {
353: pool.push(bean);
354: }
355: }
356: }
357:
358: private void freeInstance(ThreadContext callContext,
359: Instance instance) {
360: try {
361: callContext.setCurrentOperation(Operation.PRE_DESTROY);
362: callContext.setCurrentAllowedStates(StatelessContext
363: .getStates());
364: CoreDeploymentInfo deploymentInfo = callContext
365: .getDeploymentInfo();
366:
367: Method remove = instance.bean instanceof SessionBean ? deploymentInfo
368: .getCreateMethod()
369: : null;
370:
371: List<InterceptorData> callbackInterceptors = deploymentInfo
372: .getCallbackInterceptors();
373: InterceptorStack interceptorStack = new InterceptorStack(
374: instance.bean, remove, Operation.PRE_DESTROY,
375: callbackInterceptors, instance.interceptors);
376:
377: interceptorStack.invoke();
378: } catch (Throwable re) {
379: logger.error("The bean instance " + instance
380: + " threw a system exception:" + re, re);
381: }
382:
383: }
384:
385: /**
386: * This method has no work to do as all instances are removed from
387: * the pool on getInstance(...) and not returned via poolInstance(...)
388: * if they threw a system exception.
389: *
390: * @param callContext
391: * @param bean
392: */
393: public void discardInstance(ThreadContext callContext, Object bean) {
394:
395: }
396:
397: public void deploy(CoreDeploymentInfo deploymentInfo) {
398: Data data = new Data(poolLimit);
399: deploymentInfo.setContainerData(data);
400: }
401:
402: public void undeploy(CoreDeploymentInfo deploymentInfo) {
403: Data data = (Data) deploymentInfo.getContainerData();
404: if (data == null)
405: return;
406: Stack pool = data.getPool();
407: //TODO ejbRemove on each bean in pool.
408: //clean pool
409: deploymentInfo.setContainerData(null);
410: }
411:
412: static class PoolQueue {
413: private final long waitPeriod;
414:
415: public PoolQueue(long time) {
416: waitPeriod = time;
417: }
418:
419: public synchronized void waitForAvailableInstance()
420: throws org.apache.openejb.InvalidateReferenceException {
421: try {
422: wait(waitPeriod);
423: } catch (InterruptedException ie) {
424: throw new org.apache.openejb.InvalidateReferenceException(
425: new RemoteException(
426: "No instance available to service request",
427: ie));
428: }
429: }
430:
431: public synchronized void notifyWaitingThreads() {
432: notify();
433: }
434: }
435:
436: private static final class Data {
437: private final Stack pool;
438:
439: public Data(int poolLimit) {
440: pool = new LinkedListStack(poolLimit);
441: }
442:
443: public Stack getPool() {
444: return pool;
445: }
446: }
447:
448: }
|