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.cmp.jpa;
018:
019: import org.apache.openejb.OpenEJBException;
020: import org.apache.openejb.core.CoreDeploymentInfo;
021: import org.apache.openejb.core.ThreadContext;
022: import org.apache.openejb.core.cmp.CmpCallback;
023: import org.apache.openejb.core.cmp.CmpEngine;
024: import org.apache.openejb.core.cmp.ComplexKeyGenerator;
025: import org.apache.openejb.core.cmp.KeyGenerator;
026: import org.apache.openejb.core.cmp.SimpleKeyGenerator;
027: import org.apache.openejb.core.cmp.cmp2.Cmp2KeyGenerator;
028: import org.apache.openejb.core.cmp.cmp2.Cmp2Util;
029: import org.apache.openjpa.event.AbstractLifecycleListener;
030: import org.apache.openjpa.event.LifecycleEvent;
031: import org.apache.openjpa.persistence.OpenJPAEntityManagerSPI;
032: import org.apache.openjpa.persistence.OpenJPAEntityManagerFactorySPI;
033:
034: import javax.ejb.CreateException;
035: import javax.ejb.EJBException;
036: import javax.ejb.EJBLocalObject;
037: import javax.ejb.EJBObject;
038: import javax.ejb.EntityBean;
039: import javax.ejb.FinderException;
040: import javax.ejb.RemoveException;
041: import javax.naming.NamingException;
042: import javax.persistence.EntityManager;
043: import javax.persistence.PersistenceException;
044: import javax.persistence.Query;
045: import javax.transaction.Status;
046: import javax.transaction.TransactionManager;
047: import javax.transaction.TransactionSynchronizationRegistry;
048: import java.lang.reflect.Method;
049: import java.util.HashMap;
050: import java.util.HashSet;
051: import java.util.List;
052: import java.util.Map;
053: import java.util.Set;
054:
055: public class JpaCmpEngine implements CmpEngine {
056: private static final Object[] NO_ARGS = new Object[0];
057: public static final String CMP_PERSISTENCE_CONTEXT_REF_NAME = "openejb/cmp";
058:
059: /**
060: * Used to notify call CMP callback methods.
061: */
062: private final CmpCallback cmpCallback;
063:
064: private final TransactionManager transactionManager;
065: private final TransactionSynchronizationRegistry synchronizationRegistry;
066:
067: /**
068: * Thread local to track the beans we are creating to avoid an extra ejbStore callback
069: */
070: private final ThreadLocal<Set<EntityBean>> creating = new ThreadLocal<Set<EntityBean>>() {
071: protected Set<EntityBean> initialValue() {
072: return new HashSet<EntityBean>();
073: }
074: };
075:
076: /**
077: * Listener added to entity managers.
078: */
079: protected Object entityManagerListener;
080:
081: public JpaCmpEngine(CmpCallback cmpCallback,
082: TransactionManager transactionManager,
083: TransactionSynchronizationRegistry synchronizationRegistry) {
084: this .cmpCallback = cmpCallback;
085: this .transactionManager = transactionManager;
086: this .synchronizationRegistry = synchronizationRegistry;
087: }
088:
089: public synchronized void deploy(CoreDeploymentInfo deploymentInfo)
090: throws OpenEJBException {
091: configureKeyGenerator(deploymentInfo);
092: }
093:
094: public synchronized void undeploy(CoreDeploymentInfo deploymentInfo)
095: throws OpenEJBException {
096: deploymentInfo.setKeyGenerator(null);
097: }
098:
099: private EntityManager getEntityManager(
100: CoreDeploymentInfo deploymentInfo) {
101: EntityManager entityManager = null;
102: try {
103: entityManager = (EntityManager) deploymentInfo.getJndiEnc()
104: .lookup(
105: "java:comp/env/"
106: + CMP_PERSISTENCE_CONTEXT_REF_NAME);
107: } catch (NamingException ignroed) {
108: }
109:
110: if (entityManager == null) {
111: throw new EJBException(
112: "Entity manager not found at \"openejb/cmp\" in jndi ejb "
113: + deploymentInfo.getDeploymentID());
114: }
115:
116: registerListener(entityManager);
117:
118: return entityManager;
119: }
120:
121: private synchronized void registerListener(
122: EntityManager entityManager) {
123: if (entityManager instanceof OpenJPAEntityManagerSPI) {
124: OpenJPAEntityManagerSPI openjpaEM = (OpenJPAEntityManagerSPI) entityManager;
125: OpenJPAEntityManagerFactorySPI openjpaEMF = (OpenJPAEntityManagerFactorySPI) openjpaEM
126: .getEntityManagerFactory();
127:
128: if (entityManagerListener == null) {
129: entityManagerListener = new OpenJPALifecycleListener();
130: }
131: openjpaEMF.addLifecycleListener(entityManagerListener,
132: (Class[]) null);
133: return;
134: }
135:
136: Object delegate = entityManager.getDelegate();
137: if (delegate != entityManager
138: && delegate instanceof EntityManager) {
139: registerListener((EntityManager) delegate);
140: }
141: }
142:
143: public Object createBean(EntityBean bean, ThreadContext callContext)
144: throws CreateException {
145: // TODO verify that extract primary key requires a flush followed by a merge
146: boolean startedTx = startTransaction("persist");
147: creating.get().add(bean);
148: try {
149: CoreDeploymentInfo deploymentInfo = callContext
150: .getDeploymentInfo();
151: EntityManager entityManager = getEntityManager(deploymentInfo);
152:
153: entityManager.persist(bean);
154: entityManager.flush();
155: bean = entityManager.merge(bean);
156:
157: // extract the primary key from the bean
158: KeyGenerator kg = deploymentInfo.getKeyGenerator();
159: Object primaryKey = kg.getPrimaryKey(bean);
160:
161: // add to transaction cache
162: getTransactionCache().put(deploymentInfo.getCmpImplClass(),
163: primaryKey, bean);
164:
165: return primaryKey;
166: } finally {
167: creating.get().remove(bean);
168: commitTransaction(startedTx, "persist");
169: }
170: }
171:
172: public Object loadBean(ThreadContext callContext, Object primaryKey) {
173: boolean startedTx = startTransaction("load");
174: try {
175: CoreDeploymentInfo deploymentInfo = callContext
176: .getDeploymentInfo();
177: Class<?> beanClass = deploymentInfo.getCmpImplClass();
178:
179: // First check the transaction cache
180: Object bean = getTransactionCache().get(beanClass,
181: primaryKey);
182: if (bean == null) {
183: // Try to load it from the entity manager
184: EntityManager entityManager = getEntityManager(deploymentInfo);
185: bean = entityManager.find(beanClass, primaryKey);
186: }
187: return bean;
188: } finally {
189: commitTransaction(startedTx, "load");
190: }
191: }
192:
193: public void storeBeanIfNoTx(ThreadContext callContext, Object bean) {
194: boolean startedTx = startTransaction("store");
195: if (startedTx) {
196: CoreDeploymentInfo deploymentInfo = callContext
197: .getDeploymentInfo();
198:
199: try {
200: EntityManager entityManager = getEntityManager(deploymentInfo);
201: entityManager.merge(bean);
202: } finally {
203: commitTransaction(startedTx, "store");
204: }
205: }
206: }
207:
208: public void removeBean(ThreadContext callContext) {
209: boolean startedTx = startTransaction("remove");
210: try {
211: CoreDeploymentInfo deploymentInfo = callContext
212: .getDeploymentInfo();
213: Class<?> beanClass = deploymentInfo.getCmpImplClass();
214:
215: EntityManager entityManager = getEntityManager(deploymentInfo);
216: Object primaryKey = callContext.getPrimaryKey();
217:
218: // First check the transaction cache
219: Object bean = getTransactionCache().get(beanClass,
220: primaryKey);
221: if (bean == null) {
222: // Try to load it from the entity manager
223: bean = entityManager.find(beanClass, primaryKey);
224: }
225:
226: // remove the bean
227: entityManager.remove(bean);
228: getTransactionCache().remove(beanClass, primaryKey);
229: } finally {
230: commitTransaction(startedTx, "remove");
231: }
232: }
233:
234: public List<Object> queryBeans(ThreadContext callContext,
235: Method queryMethod, Object[] args) throws FinderException {
236: CoreDeploymentInfo deploymentInfo = callContext
237: .getDeploymentInfo();
238: EntityManager entityManager = getEntityManager(deploymentInfo);
239:
240: StringBuilder queryName = new StringBuilder();
241: queryName.append(deploymentInfo.getAbstractSchemaName())
242: .append(".").append(queryMethod.getName());
243: String shortName = queryName.toString();
244: if (queryMethod.getParameterTypes().length > 0) {
245: queryName.append('(');
246: boolean first = true;
247: for (Class<?> parameterType : queryMethod
248: .getParameterTypes()) {
249: if (!first)
250: queryName.append(',');
251: queryName.append(parameterType.getCanonicalName());
252: first = false;
253: }
254: queryName.append(')');
255:
256: }
257:
258: String fullName = queryName.toString();
259: Query query = createNamedQuery(entityManager, fullName);
260: if (query == null) {
261: query = createNamedQuery(entityManager, shortName);
262: if (query == null) {
263: throw new FinderException(
264: "No query defined for method " + fullName);
265: }
266: }
267: return executeSelectQuery(query, args);
268: }
269:
270: public List<Object> queryBeans(CoreDeploymentInfo deploymentInfo,
271: String signature, Object[] args) throws FinderException {
272: EntityManager entityManager = getEntityManager(deploymentInfo);
273:
274: Query query = createNamedQuery(entityManager, signature);
275: if (query == null) {
276: int parenIndex = signature.indexOf('(');
277: if (parenIndex > 0) {
278: String shortName = signature.substring(0, parenIndex);
279: query = createNamedQuery(entityManager, shortName);
280: }
281: if (query == null) {
282: throw new FinderException(
283: "No query defined for method " + signature);
284: }
285: }
286: return executeSelectQuery(query, args);
287: }
288:
289: private List<Object> executeSelectQuery(Query query, Object[] args) {
290: // process args
291: if (args == null) {
292: args = NO_ARGS;
293: }
294: for (int i = 0; i < args.length; i++) {
295: Object arg = args[i];
296: // ejb proxies need to be swapped out for real instance classes
297: if (arg instanceof EJBObject) {
298: arg = Cmp2Util.getEntityBean(((EJBObject) arg));
299: }
300: if (arg instanceof EJBLocalObject) {
301: arg = Cmp2Util.getEntityBean(((EJBLocalObject) arg));
302: }
303: query.setParameter(i + 1, arg);
304: }
305:
306: // todo results should not be iterated over, but should instead
307: // perform all work in a wrapper list on demand by the application code
308: List results = query.getResultList();
309: for (Object value : results) {
310: if (value instanceof EntityBean) {
311: // todo don't activate beans already activated
312: EntityBean entity = (EntityBean) value;
313: cmpCallback.setEntityContext(entity);
314: cmpCallback.ejbActivate(entity);
315: }
316: }
317: //noinspection unchecked
318: return results;
319: }
320:
321: public int executeUpdateQuery(CoreDeploymentInfo deploymentInfo,
322: String signature, Object[] args) throws FinderException {
323: EntityManager entityManager = getEntityManager(deploymentInfo);
324:
325: Query query = createNamedQuery(entityManager, signature);
326: if (query == null) {
327: int parenIndex = signature.indexOf('(');
328: if (parenIndex > 0) {
329: String shortName = signature.substring(0, parenIndex);
330: query = createNamedQuery(entityManager, shortName);
331: }
332: if (query == null) {
333: throw new FinderException(
334: "No query defined for method " + signature);
335: }
336: }
337:
338: // process args
339: if (args == null) {
340: args = NO_ARGS;
341: }
342: for (int i = 0; i < args.length; i++) {
343: Object arg = args[i];
344: // ejb proxies need to be swapped out for real instance classes
345: if (arg instanceof EJBObject) {
346: arg = Cmp2Util.getEntityBean(((EJBObject) arg));
347: }
348: if (arg instanceof EJBLocalObject) {
349: arg = Cmp2Util.getEntityBean(((EJBLocalObject) arg));
350: }
351: query.setParameter(i + 1, arg);
352: }
353:
354: int result = query.executeUpdate();
355: return result;
356: }
357:
358: private Query createNamedQuery(EntityManager entityManager,
359: String name) {
360: try {
361: return entityManager.createNamedQuery(name);
362: } catch (IllegalArgumentException ignored) {
363: // soooo lame that jpa throws an exception instead of returning null....
364: ignored.printStackTrace();
365: return null;
366: }
367: }
368:
369: private boolean startTransaction(String operation) {
370: try {
371: if (Status.STATUS_NO_TRANSACTION == transactionManager
372: .getStatus()) {
373: transactionManager.begin();
374: return true;
375: }
376: return false;
377: } catch (Exception e) {
378: throw new EJBException("Unable to start transaction for "
379: + operation + " operation", e);
380: }
381: }
382:
383: private void commitTransaction(boolean startedTx, String operation) {
384: try {
385: if (startedTx) {
386: transactionManager.commit();
387: }
388: } catch (Exception e) {
389: throw new EJBException(
390: "Unable to complete transaction for " + operation
391: + " operation", e);
392: }
393: }
394:
395: private void configureKeyGenerator(CoreDeploymentInfo di)
396: throws OpenEJBException {
397: if (di.isCmp2()) {
398: di.setKeyGenerator(new Cmp2KeyGenerator());
399: } else {
400: String primaryKeyField = di.getPrimaryKeyField();
401: Class cmpBeanImpl = di.getCmpImplClass();
402: if (primaryKeyField != null) {
403: di.setKeyGenerator(new SimpleKeyGenerator(cmpBeanImpl,
404: primaryKeyField));
405: } else if (Object.class.equals(di.getPrimaryKeyClass())) {
406: di.setKeyGenerator(new SimpleKeyGenerator(cmpBeanImpl,
407: "OpenEJB_pk"));
408: } else {
409: di.setKeyGenerator(new ComplexKeyGenerator(cmpBeanImpl,
410: di.getPrimaryKeyClass()));
411: }
412: }
413: }
414:
415: // todo remove when OpenJPA fixes the new-remove-new-find bug
416: private TransactionCache getTransactionCache() {
417: TransactionCache transactionCache = (TransactionCache) synchronizationRegistry
418: .getResource(TransactionCache.class);
419: if (transactionCache == null) {
420: transactionCache = new TransactionCache();
421: synchronizationRegistry.putResource(TransactionCache.class,
422: transactionCache);
423: }
424: return transactionCache;
425: }
426:
427: private static class TransactionCache {
428: private final Map<Class, Map<Object, Object>> cache = new HashMap<Class, Map<Object, Object>>();
429:
430: public Object get(Class clazz, Object primaryKey) {
431: Map<Object, Object> instances = cache.get(clazz);
432: if (instances == null)
433: return null;
434: return instances.get(primaryKey);
435: }
436:
437: public void put(Class clazz, Object primaryKey, Object value) {
438: Map<Object, Object> instances = cache.get(clazz);
439: if (instances == null) {
440: instances = new HashMap<Object, Object>();
441: cache.put(clazz, instances);
442: }
443: instances.put(primaryKey, value);
444: }
445:
446: public Object remove(Class clazz, Object primaryKey) {
447: Map<Object, Object> instances = cache.get(clazz);
448: if (instances == null)
449: return null;
450: return instances.remove(primaryKey);
451: }
452: }
453:
454: private class OpenJPALifecycleListener extends
455: AbstractLifecycleListener {
456: // protected void eventOccurred(LifecycleEvent event) {
457: // int type = event.getType();
458: // switch (type) {
459: // case LifecycleEvent.BEFORE_PERSIST:
460: // System.out.println("BEFORE_PERSIST");
461: // break;
462: // case LifecycleEvent.AFTER_PERSIST:
463: // System.out.println("AFTER_PERSIST");
464: // break;
465: // case LifecycleEvent.AFTER_LOAD:
466: // System.out.println("AFTER_LOAD");
467: // break;
468: // case LifecycleEvent.BEFORE_STORE:
469: // System.out.println("BEFORE_STORE");
470: // break;
471: // case LifecycleEvent.AFTER_STORE:
472: // System.out.println("AFTER_STORE");
473: // break;
474: // case LifecycleEvent.BEFORE_CLEAR:
475: // System.out.println("BEFORE_CLEAR");
476: // break;
477: // case LifecycleEvent.AFTER_CLEAR:
478: // System.out.println("AFTER_CLEAR");
479: // break;
480: // case LifecycleEvent.BEFORE_DELETE:
481: // System.out.println("BEFORE_DELETE");
482: // break;
483: // case LifecycleEvent.AFTER_DELETE:
484: // System.out.println("AFTER_DELETE");
485: // break;
486: // case LifecycleEvent.BEFORE_DIRTY:
487: // System.out.println("BEFORE_DIRTY");
488: // break;
489: // case LifecycleEvent.AFTER_DIRTY:
490: // System.out.println("AFTER_DIRTY");
491: // break;
492: // case LifecycleEvent.BEFORE_DIRTY_FLUSHED:
493: // System.out.println("BEFORE_DIRTY_FLUSHED");
494: // break;
495: // case LifecycleEvent.AFTER_DIRTY_FLUSHED:
496: // System.out.println("AFTER_DIRTY_FLUSHED");
497: // break;
498: // case LifecycleEvent.BEFORE_DETACH:
499: // System.out.println("BEFORE_DETACH");
500: // break;
501: // case LifecycleEvent.AFTER_DETACH:
502: // System.out.println("AFTER_DETACH");
503: // break;
504: // case LifecycleEvent.BEFORE_ATTACH:
505: // System.out.println("BEFORE_ATTACH");
506: // break;
507: // case LifecycleEvent.AFTER_ATTACH:
508: // System.out.println("AFTER_ATTACH");
509: // break;
510: // case LifecycleEvent.AFTER_REFRESH:
511: // System.out.println("AFTER_REFRESH");
512: // break;
513: // default:
514: // System.out.println("default");
515: // break;
516: // }
517: // super.eventOccurred(event);
518: // }
519:
520: public void afterLoad(LifecycleEvent lifecycleEvent) {
521: eventOccurred(lifecycleEvent);
522: Object bean = lifecycleEvent.getSource();
523: // This may seem a bit strange to call ejbActivate immedately followed by ejbLoad,
524: // but it is completely legal. Since the ejbActivate method is not allowed to access
525: // persistent state of the bean (EJB 3.0fr 8.5.2) there should be no concern that the
526: // call back method clears the bean state before ejbLoad is called.
527: cmpCallback.setEntityContext((EntityBean) bean);
528: cmpCallback.ejbActivate((EntityBean) bean);
529: cmpCallback.ejbLoad((EntityBean) bean);
530: }
531:
532: public void beforeStore(LifecycleEvent lifecycleEvent) {
533: eventOccurred(lifecycleEvent);
534: EntityBean bean = (EntityBean) lifecycleEvent.getSource();
535: if (!creating.get().contains(bean)) {
536: cmpCallback.ejbStore(bean);
537: }
538: }
539:
540: public void afterAttach(LifecycleEvent lifecycleEvent) {
541: eventOccurred(lifecycleEvent);
542: Object bean = lifecycleEvent.getSource();
543: cmpCallback.setEntityContext((EntityBean) bean);
544: }
545:
546: public void beforeDelete(LifecycleEvent lifecycleEvent) {
547: eventOccurred(lifecycleEvent);
548: try {
549: Object bean = lifecycleEvent.getSource();
550: cmpCallback.ejbRemove((EntityBean) bean);
551: } catch (RemoveException e) {
552: throw new PersistenceException(e);
553: }
554: }
555:
556: public void afterDetach(LifecycleEvent lifecycleEvent) {
557: eventOccurred(lifecycleEvent);
558: // todo detach is called after ejbRemove which does not need ejbPassivate
559: Object bean = lifecycleEvent.getSource();
560: cmpCallback.ejbPassivate((EntityBean) bean);
561: cmpCallback.unsetEntityContext((EntityBean) bean);
562: }
563:
564: public void beforePersist(LifecycleEvent lifecycleEvent) {
565: eventOccurred(lifecycleEvent);
566: }
567:
568: public void afterRefresh(LifecycleEvent lifecycleEvent) {
569: eventOccurred(lifecycleEvent);
570: }
571:
572: public void beforeDetach(LifecycleEvent lifecycleEvent) {
573: eventOccurred(lifecycleEvent);
574: }
575:
576: public void beforeAttach(LifecycleEvent lifecycleEvent) {
577: eventOccurred(lifecycleEvent);
578: }
579: }
580: }
|