001: /**
002: *
003: * Licensed to the Apache Software Foundation (ASF) under one or more
004: * contributor license agreements. See the NOTICE file distributed with
005: * this work for additional information regarding copyright ownership.
006: * The ASF licenses this file to You under the Apache License, Version 2.0
007: * (the "License"); you may not use this file except in compliance with
008: * the License. You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing, software
013: * distributed under the License is distributed on an "AS IS" BASIS,
014: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015: * See the License for the specific language governing permissions and
016: * limitations under the License.
017: */package org.apache.openejb.core.mdb;
018:
019: import org.apache.openejb.core.BaseContext;
020: import org.apache.openejb.core.CoreDeploymentInfo;
021: import org.apache.openejb.core.Operation;
022: import org.apache.openejb.core.ThreadContext;
023: import org.apache.openejb.core.interceptor.InterceptorData;
024: import org.apache.openejb.core.interceptor.InterceptorStack;
025: import org.apache.openejb.core.mdb.Instance;
026: import org.apache.openejb.spi.SecurityService;
027: import org.apache.openejb.Injection;
028: import org.apache.openejb.util.LogCategory;
029: import org.apache.openejb.util.Logger;
030: import org.apache.xbean.recipe.ObjectRecipe;
031: import org.apache.xbean.recipe.Option;
032: import org.apache.xbean.recipe.StaticRecipe;
033: import org.apache.xbean.recipe.ConstructionException;
034:
035: import javax.ejb.MessageDrivenBean;
036: import javax.naming.Context;
037: import javax.naming.NamingException;
038: import javax.resource.spi.UnavailableException;
039: import javax.transaction.TransactionManager;
040: import java.lang.reflect.Method;
041: import java.util.List;
042: import java.util.ArrayList;
043: import java.util.HashMap;
044:
045: /**
046: * A MdbInstanceFactory creates instances of message driven beans for a single instance. This class differs from other
047: * instance managers in OpenEJB as it doesn't do pooling and it creates instances for only a single EJB deployment.
048: * </p>
049: * The MdbContainer assumes that the resouce adapter is pooling message endpoints so a second level of pooling in the
050: * container would be inefficient. This is true of all known resouce adapters in opensource (ActiveMQ), so if this is
051: * a poor assumption for your resource adapter, contact the OpenEJB developers.
052: * </p>
053: * This class can optionally limit the number of bean instances and therefore the message endpoints available to the
054: * resource adapter.
055: */
056: public class MdbInstanceFactory {
057: private static final Logger logger = Logger.getInstance(
058: LogCategory.OPENEJB, "org.apache.openejb.util.resources");
059:
060: private final CoreDeploymentInfo deploymentInfo;
061: private final TransactionManager transactionManager;
062: private final SecurityService securityService;
063: private final int instanceLimit;
064: private int instanceCount;
065:
066: /**
067: * Creates a MdbInstanceFactory for a single specific deployment.
068: * @param deploymentInfo the deployment for which instances will be created
069: * @param transactionManager the transaction manager for this container system
070: * @param securityService the transaction manager for this container system
071: * @param instanceLimit the maximal number of instances or <= 0 if unlimited
072: */
073: public MdbInstanceFactory(CoreDeploymentInfo deploymentInfo,
074: TransactionManager transactionManager,
075: SecurityService securityService, int instanceLimit) {
076: this .deploymentInfo = deploymentInfo;
077: this .transactionManager = transactionManager;
078: this .securityService = securityService;
079: this .instanceLimit = instanceLimit;
080: }
081:
082: /**
083: * Gets the maximal number of instances that can exist at any time.
084: * @return the maximum number of instances or <= 0 if unlimitied
085: */
086: public int getInstanceLimit() {
087: return instanceLimit;
088: }
089:
090: /**
091: * Gets the current number of created instances.
092: * @return the current number of instances created
093: */
094: public synchronized int getInstanceCount() {
095: return instanceCount;
096: }
097:
098: /**
099: * Creates a new mdb instance preforming all necessary lifecycle callbacks
100: * @return a new message driven bean instance
101: * @throws UnavailableException if the instance limit has been exceeded or
102: * if an exception occurs while creating the bean instance
103: * @param ignoreInstanceCount
104: */
105: public Object createInstance(boolean ignoreInstanceCount)
106: throws UnavailableException {
107: if (!ignoreInstanceCount) {
108: synchronized (this ) {
109: // check the instance limit
110: if (instanceLimit > 0 && instanceCount >= instanceLimit) {
111: throw new UnavailableException("Only "
112: + instanceLimit
113: + " instances can be created");
114: }
115: // increment the instance count
116: instanceCount++;
117: }
118: }
119:
120: try {
121: Object bean = constructBean();
122: return bean;
123: } catch (UnavailableException e) {
124: // decrement the instance count
125: if (!ignoreInstanceCount) {
126: synchronized (this ) {
127: instanceCount--;
128: }
129: }
130:
131: throw e;
132: }
133: }
134:
135: /**
136: * Frees an instance no longer needed by the resource adapter. This method makes all the necessary lifecycle
137: * callbacks and decrements the instance count. This method should not be used to disposed of beans that have
138: * thrown a system exception. Instead the discardInstance method should be called.
139: * @param instance the bean instance to free
140: * @param ignoredInstanceCount
141: */
142: public void freeInstance(Instance instance,
143: boolean ignoredInstanceCount) {
144: if (instance == null)
145: throw new NullPointerException("bean is null");
146:
147: // decrement the instance count
148: if (!ignoredInstanceCount) {
149: synchronized (this ) {
150: instanceCount--;
151: }
152: }
153:
154: ThreadContext callContext = ThreadContext.getThreadContext();
155: Operation originalOperation = callContext.getCurrentOperation();
156: BaseContext.State[] originalAllowedStates = callContext
157: .getCurrentAllowedStates();
158: try {
159: // call post destroy method
160: callContext.setCurrentOperation(Operation.PRE_DESTROY);
161: callContext.setCurrentAllowedStates(MdbContext.getStates());
162: Method remove = instance.bean instanceof MessageDrivenBean ? MessageDrivenBean.class
163: .getMethod("ejbRemove")
164: : null;
165: List<InterceptorData> callbackInterceptors = deploymentInfo
166: .getCallbackInterceptors();
167: InterceptorStack interceptorStack = new InterceptorStack(
168: instance.bean, remove, Operation.PRE_DESTROY,
169: callbackInterceptors, instance.interceptors);
170: interceptorStack.invoke();
171: } catch (Throwable re) {
172: MdbInstanceFactory.logger.error(
173: "The bean instance " + instance.bean
174: + " threw a system exception:" + re, re);
175: } finally {
176: callContext.setCurrentOperation(originalOperation);
177: callContext.setCurrentAllowedStates(originalAllowedStates);
178: }
179: }
180:
181: /**
182: * Recreates a bean instance that has thrown a system exception. As required by the EJB specification, lifecycle
183: * callbacks are not invoked. To normally free a bean instance call the freeInstance method.
184: * @param bean the bean instance to discard
185: * @return the new replacement bean instance
186: */
187: public Object recreateInstance(Object bean)
188: throws UnavailableException {
189: if (bean == null)
190: throw new NullPointerException("bean is null");
191: Object newBean = constructBean();
192: return newBean;
193: }
194:
195: private Object constructBean() throws UnavailableException {
196: Class beanClass = deploymentInfo.getBeanClass();
197: ObjectRecipe objectRecipe = new ObjectRecipe(beanClass);
198: objectRecipe.allow(Option.FIELD_INJECTION);
199: objectRecipe.allow(Option.PRIVATE_PROPERTIES);
200: objectRecipe.allow(Option.IGNORE_MISSING_PROPERTIES);
201:
202: ThreadContext callContext = new ThreadContext(deploymentInfo,
203: null, Operation.INJECTION);
204: ThreadContext.enter(callContext);
205: try {
206: Context ctx = deploymentInfo.getJndiEnc();
207: // construct the bean instance
208: MdbContext mdbContext = null;
209: synchronized (this ) {
210: try {
211: mdbContext = (MdbContext) ctx
212: .lookup("java:comp/EJBContext");
213: } catch (NamingException e) {
214: mdbContext = new MdbContext(transactionManager,
215: securityService);
216: ctx.bind("java:comp/EJBContext", mdbContext);
217: }
218: }
219: fillInjectionProperties(objectRecipe, beanClass,
220: deploymentInfo, ctx);
221:
222: // only in this case should the callback be used
223: callContext.setCurrentOperation(Operation.INJECTION);
224: callContext.setCurrentAllowedStates(MdbContext.getStates());
225: if (MessageDrivenBean.class.isAssignableFrom(beanClass)) {
226: objectRecipe.setProperty("messageDrivenContext",
227: new StaticRecipe(mdbContext));
228: }
229: Object bean = objectRecipe.create();
230:
231: HashMap<String, Object> interceptorInstances = new HashMap<String, Object>();
232: for (InterceptorData interceptorData : deploymentInfo
233: .getAllInterceptors()) {
234: if (interceptorData.getInterceptorClass().equals(
235: beanClass))
236: continue;
237:
238: Class clazz = interceptorData.getInterceptorClass();
239: ObjectRecipe interceptorRecipe = new ObjectRecipe(clazz);
240: interceptorRecipe.allow(Option.FIELD_INJECTION);
241: interceptorRecipe.allow(Option.PRIVATE_PROPERTIES);
242: interceptorRecipe
243: .allow(Option.IGNORE_MISSING_PROPERTIES);
244:
245: fillInjectionProperties(interceptorRecipe, clazz,
246: deploymentInfo, ctx);
247: try {
248: Object interceptorInstance = interceptorRecipe
249: .create(clazz.getClassLoader());
250: interceptorInstances.put(clazz.getName(),
251: interceptorInstance);
252: } catch (ConstructionException e) {
253: throw new Exception(
254: "Failed to create interceptor: "
255: + clazz.getName(), e);
256: }
257: }
258:
259: // TODO: We need to keep these somehwere
260: interceptorInstances.put(beanClass.getName(), bean);
261: Instance instance = new Instance(bean, interceptorInstances);
262: try {
263: callContext
264: .setCurrentOperation(Operation.POST_CONSTRUCT);
265: callContext.setCurrentAllowedStates(MdbContext
266: .getStates());
267:
268: List<InterceptorData> callbackInterceptors = deploymentInfo
269: .getCallbackInterceptors();
270: InterceptorStack interceptorStack = new InterceptorStack(
271: bean, null, Operation.POST_CONSTRUCT,
272: callbackInterceptors, interceptorInstances);
273: interceptorStack.invoke();
274: } catch (Exception e) {
275: throw e;
276: }
277:
278: try {
279: if (bean instanceof MessageDrivenBean) {
280: callContext.setCurrentOperation(Operation.CREATE);
281: callContext.setCurrentAllowedStates(MdbContext
282: .getStates());
283: Method create = deploymentInfo.getCreateMethod();
284: InterceptorStack interceptorStack = new InterceptorStack(
285: bean, create, Operation.CREATE,
286: new ArrayList(), new HashMap());
287: interceptorStack.invoke();
288: }
289: } catch (Exception e) {
290: throw e;
291: }
292:
293: return instance;
294: } catch (Throwable e) {
295: if (e instanceof java.lang.reflect.InvocationTargetException) {
296: e = ((java.lang.reflect.InvocationTargetException) e)
297: .getTargetException();
298: }
299: String message = "The bean instance threw a system exception:"
300: + e;
301: MdbInstanceFactory.logger.error(message, e);
302: throw new UnavailableException(message, e);
303: } finally {
304: ThreadContext.exit(callContext);
305: }
306: }
307:
308: private static void fillInjectionProperties(
309: ObjectRecipe objectRecipe, Class clazz,
310: CoreDeploymentInfo deploymentInfo, Context context) {
311: for (Injection injection : deploymentInfo.getInjections()) {
312: if (!injection.getTarget().isAssignableFrom(clazz))
313: continue;
314: try {
315: String jndiName = injection.getJndiName();
316: Object object = context.lookup("java:comp/env/"
317: + jndiName);
318: if (object instanceof String) {
319: String string = (String) object;
320: // Pass it in raw so it could be potentially converted to
321: // another data type by an xbean-reflect property editor
322: objectRecipe.setProperty(injection.getTarget()
323: .getName()
324: + "/" + injection.getName(), string);
325: } else {
326: objectRecipe.setProperty(injection.getTarget()
327: .getName()
328: + "/" + injection.getName(),
329: new StaticRecipe(object));
330: }
331: } catch (NamingException e) {
332: logger
333: .warning("Injection data not found in enc: jndiName='"
334: + injection.getJndiName()
335: + "', target="
336: + injection.getTarget()
337: + "/" + injection.getName());
338: }
339: }
340: }
341:
342: }
|