Save This Page
Home » openejb-3.1.2-src » org.apache » openejb » core » cmp » jpa » [javadoc | source]
    1   /**
    2    *
    3    * Licensed to the Apache Software Foundation (ASF) under one or more
    4    * contributor license agreements.  See the NOTICE file distributed with
    5    * this work for additional information regarding copyright ownership.
    6    * The ASF licenses this file to You under the Apache License, Version 2.0
    7    * (the "License"); you may not use this file except in compliance with
    8    * the License.  You may obtain a copy of the License at
    9    *
   10    *     http://www.apache.org/licenses/LICENSE-2.0
   11    *
   12    *  Unless required by applicable law or agreed to in writing, software
   13    *  distributed under the License is distributed on an "AS IS" BASIS,
   14    *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   15    *  See the License for the specific language governing permissions and
   16    *  limitations under the License.
   17    */
   18   package org.apache.openejb.core.cmp.jpa;
   19   
   20   import java.lang.reflect.Method;
   21   import java.util.HashSet;
   22   import java.util.List;
   23   import java.util.Set;
   24   import javax.ejb.CreateException;
   25   import javax.ejb.EJBException;
   26   import javax.ejb.EJBLocalObject;
   27   import javax.ejb.EJBObject;
   28   import javax.ejb.EntityBean;
   29   import javax.ejb.FinderException;
   30   import javax.ejb.RemoveException;
   31   import javax.naming.NamingException;
   32   import javax.persistence.EntityManager;
   33   import javax.persistence.PersistenceException;
   34   import javax.persistence.Query;
   35   
   36   import org.apache.openejb.OpenEJBException;
   37   import org.apache.openejb.core.CoreDeploymentInfo;
   38   import org.apache.openejb.core.ThreadContext;
   39   import org.apache.openejb.core.cmp.CmpCallback;
   40   import org.apache.openejb.core.cmp.CmpEngine;
   41   import org.apache.openejb.core.cmp.ComplexKeyGenerator;
   42   import org.apache.openejb.core.cmp.KeyGenerator;
   43   import org.apache.openejb.core.cmp.SimpleKeyGenerator;
   44   import org.apache.openejb.core.cmp.cmp2.Cmp2KeyGenerator;
   45   import org.apache.openejb.core.cmp.cmp2.Cmp2Util;
   46   import org.apache.openejb.core.transaction.TransactionType;
   47   import org.apache.openejb.core.transaction.TransactionPolicy;
   48   import static org.apache.openejb.core.transaction.EjbTransactionUtil.afterInvoke;
   49   import static org.apache.openejb.core.transaction.EjbTransactionUtil.createTransactionPolicy;
   50   import org.apache.openjpa.event.AbstractLifecycleListener;
   51   import org.apache.openjpa.event.LifecycleEvent;
   52   import org.apache.openjpa.persistence.OpenJPAEntityManagerFactorySPI;
   53   import org.apache.openjpa.persistence.OpenJPAEntityManagerSPI;
   54   
   55   public class JpaCmpEngine implements CmpEngine {
   56       private static final Object[] NO_ARGS = new Object[0];
   57       public static final String CMP_PERSISTENCE_CONTEXT_REF_NAME = "openejb/cmp";
   58   
   59       /**
   60        * Used to notify call CMP callback methods.
   61        */
   62       private final CmpCallback cmpCallback;
   63   
   64       /**
   65        * Thread local to track the beans we are creating to avoid an extra ejbStore callback
   66        */
   67       private final ThreadLocal<Set<EntityBean>> creating = new ThreadLocal<Set<EntityBean>>() {
   68           protected Set<EntityBean> initialValue() {
   69               return new HashSet<EntityBean>();
   70           }
   71       };
   72   
   73       /**
   74        * Listener added to entity managers.
   75        */
   76       protected Object entityManagerListener;
   77   
   78       public JpaCmpEngine(CmpCallback cmpCallback) {
   79           this.cmpCallback = cmpCallback;
   80       }
   81   
   82       public synchronized void deploy(CoreDeploymentInfo deploymentInfo) throws OpenEJBException {
   83           configureKeyGenerator(deploymentInfo);
   84       }
   85   
   86       public synchronized void undeploy(CoreDeploymentInfo deploymentInfo) throws OpenEJBException {
   87           deploymentInfo.setKeyGenerator(null);
   88       }
   89   
   90       private EntityManager getEntityManager(CoreDeploymentInfo deploymentInfo) {
   91           EntityManager entityManager = null;
   92           try {
   93               entityManager = (EntityManager) deploymentInfo.getJndiEnc().lookup("java:comp/env/" + CMP_PERSISTENCE_CONTEXT_REF_NAME);
   94           } catch (NamingException ignroed) {
   95           }
   96   
   97           if (entityManager == null) {
   98               throw new EJBException("Entity manager not found at \"openejb/cmp\" in jndi ejb " + deploymentInfo.getDeploymentID());
   99           }
  100   
  101           registerListener(entityManager);
  102   
  103           return entityManager;
  104       }
  105   
  106       private synchronized void registerListener(EntityManager entityManager) {
  107           if (entityManager instanceof OpenJPAEntityManagerSPI) {
  108               OpenJPAEntityManagerSPI openjpaEM = (OpenJPAEntityManagerSPI) entityManager;
  109               OpenJPAEntityManagerFactorySPI openjpaEMF = (OpenJPAEntityManagerFactorySPI) openjpaEM.getEntityManagerFactory();
  110   
  111               if (entityManagerListener == null) {
  112                   entityManagerListener = new OpenJPALifecycleListener();
  113               }
  114               openjpaEMF.addLifecycleListener(entityManagerListener, (Class[])null);
  115               return;
  116           }
  117   
  118           Object delegate = entityManager.getDelegate();
  119           if (delegate != entityManager && delegate instanceof EntityManager) {
  120               registerListener((EntityManager) delegate);
  121           }
  122       }
  123   
  124       public Object createBean(EntityBean bean, ThreadContext callContext) throws CreateException {
  125           // TODO verify that extract primary key requires a flush followed by a merge
  126           TransactionPolicy txPolicy = startTransaction("persist", callContext);
  127           creating.get().add(bean);
  128           try {
  129               CoreDeploymentInfo deploymentInfo = callContext.getDeploymentInfo();
  130               EntityManager entityManager = getEntityManager(deploymentInfo);
  131   
  132               entityManager.persist(bean);
  133               entityManager.flush();
  134               bean = entityManager.merge(bean);
  135   
  136               // extract the primary key from the bean
  137               KeyGenerator kg = deploymentInfo.getKeyGenerator();
  138               Object primaryKey = kg.getPrimaryKey(bean);
  139   
  140               return primaryKey;
  141           } finally {
  142               creating.get().remove(bean);
  143               commitTransaction("persist", callContext, txPolicy);
  144           }
  145       }
  146   
  147       public Object loadBean(ThreadContext callContext, Object primaryKey) {
  148           TransactionPolicy txPolicy = startTransaction("load", callContext);
  149           try {
  150               CoreDeploymentInfo deploymentInfo = callContext.getDeploymentInfo();
  151               Class<?> beanClass = deploymentInfo.getCmpImplClass();
  152   
  153               // Try to load it from the entity manager
  154               EntityManager entityManager = getEntityManager(deploymentInfo);
  155               return entityManager.find(beanClass, primaryKey);
  156           } finally {
  157               commitTransaction("load", callContext, txPolicy);
  158           }
  159       }
  160   
  161       public void storeBeanIfNoTx(ThreadContext callContext, Object bean) {
  162           TransactionPolicy callerTxPolicy = callContext.getTransactionPolicy();
  163           if (callerTxPolicy != null && callerTxPolicy.isTransactionActive()) {
  164               return;
  165           }
  166   
  167           TransactionPolicy txPolicy = startTransaction("store", callContext);
  168           try {
  169               // only store if we started a new transaction
  170               if (txPolicy.isNewTransaction()) {
  171                   EntityManager entityManager = getEntityManager(callContext.getDeploymentInfo());
  172                   entityManager.merge(bean);
  173               }
  174           } finally {
  175               commitTransaction("store", callContext, txPolicy);
  176           }
  177       }
  178   
  179       public void removeBean(ThreadContext callContext) {
  180           TransactionPolicy txPolicy = startTransaction("remove", callContext);
  181           try {
  182               CoreDeploymentInfo deploymentInfo = callContext.getDeploymentInfo();
  183               Class<?> beanClass = deploymentInfo.getCmpImplClass();
  184   
  185               EntityManager entityManager = getEntityManager(deploymentInfo);
  186               Object primaryKey = callContext.getPrimaryKey();
  187   
  188                   // Try to load it from the entity manager
  189               Object bean = entityManager.find(beanClass, primaryKey);
  190               // remove the bean
  191               entityManager.remove(bean);
  192           } finally {
  193               commitTransaction("remove", callContext, txPolicy);
  194           }
  195       }
  196   
  197       public List<Object> queryBeans(ThreadContext callContext, Method queryMethod, Object[] args) throws FinderException {
  198           CoreDeploymentInfo deploymentInfo = callContext.getDeploymentInfo();
  199           EntityManager entityManager = getEntityManager(deploymentInfo);
  200   
  201           StringBuilder queryName = new StringBuilder();
  202           queryName.append(deploymentInfo.getAbstractSchemaName()).append(".").append(queryMethod.getName());
  203           String shortName = queryName.toString();
  204           if (queryMethod.getParameterTypes().length > 0) {
  205               queryName.append('(');
  206               boolean first = true;
  207               for (Class<?> parameterType : queryMethod.getParameterTypes()) {
  208                   if (!first) queryName.append(',');
  209                   queryName.append(parameterType.getCanonicalName());
  210                   first = false;
  211               }
  212               queryName.append(')');
  213   
  214           }
  215   
  216           String fullName = queryName.toString();
  217           Query query = createNamedQuery(entityManager, fullName);
  218           if (query == null) {
  219               query = createNamedQuery(entityManager, shortName);
  220               if (query == null) {
  221                   throw new FinderException("No query defined for method " + fullName);
  222               }
  223           }
  224           return executeSelectQuery(query, args);
  225       }
  226   
  227       public List<Object> queryBeans(CoreDeploymentInfo deploymentInfo, String signature, Object[] args) throws FinderException {
  228           EntityManager entityManager = getEntityManager(deploymentInfo);
  229   
  230           Query query = createNamedQuery(entityManager, signature);
  231           if (query == null) {
  232               int parenIndex = signature.indexOf('(');
  233               if (parenIndex > 0) {
  234                   String shortName = signature.substring(0, parenIndex);
  235                   query = createNamedQuery(entityManager, shortName);
  236               }
  237               if (query == null) {
  238                   throw new FinderException("No query defined for method " + signature);
  239               }
  240           }
  241           return executeSelectQuery(query, args);
  242       }
  243   
  244       private List<Object> executeSelectQuery(Query query, Object[] args) {
  245           // process args
  246           if (args == null) {
  247               args = NO_ARGS;
  248           }
  249           for (int i = 0; i < args.length; i++) {
  250               Object arg = args[i];
  251               // ejb proxies need to be swapped out for real instance classes
  252               if (arg instanceof EJBObject) {
  253                   arg = Cmp2Util.getEntityBean(((EJBObject) arg));
  254               }
  255               if (arg instanceof EJBLocalObject) {
  256                   arg = Cmp2Util.getEntityBean(((EJBLocalObject) arg));
  257               }
  258               query.setParameter(i + 1, arg);
  259           }
  260   
  261           // todo results should not be iterated over, but should instead
  262           // perform all work in a wrapper list on demand by the application code
  263           List results = query.getResultList();
  264           for (Object value : results) {
  265               if (value instanceof EntityBean) {
  266                   // todo don't activate beans already activated
  267                   EntityBean entity = (EntityBean) value;
  268                   cmpCallback.setEntityContext(entity);
  269                   cmpCallback.ejbActivate(entity);
  270               }
  271           }
  272           //noinspection unchecked
  273           return results;
  274       }
  275   
  276       public int executeUpdateQuery(CoreDeploymentInfo deploymentInfo, String signature, Object[] args) throws FinderException {
  277           EntityManager entityManager = getEntityManager(deploymentInfo);
  278   
  279           Query query = createNamedQuery(entityManager, signature);
  280           if (query == null) {
  281               int parenIndex = signature.indexOf('(');
  282               if (parenIndex > 0) {
  283                   String shortName = signature.substring(0, parenIndex);
  284                   query = createNamedQuery(entityManager, shortName);
  285               }
  286               if (query == null) {
  287                   throw new FinderException("No query defined for method " + signature);
  288               }
  289           }
  290   
  291           // process args
  292           if (args == null) {
  293               args = NO_ARGS;
  294           }
  295           for (int i = 0; i < args.length; i++) {
  296               Object arg = args[i];
  297               // ejb proxies need to be swapped out for real instance classes
  298               if (arg instanceof EJBObject) {
  299                   arg = Cmp2Util.getEntityBean(((EJBObject) arg));
  300               }
  301               if (arg instanceof EJBLocalObject) {
  302                   arg = Cmp2Util.getEntityBean(((EJBLocalObject) arg));
  303               }
  304               query.setParameter(i + 1, arg);
  305           }
  306   
  307           int result = query.executeUpdate();
  308           return result;
  309       }
  310   
  311       private Query createNamedQuery(EntityManager entityManager, String name) {
  312           try {
  313               return entityManager.createNamedQuery(name);
  314           } catch (IllegalArgumentException ignored) {
  315               // soooo lame that jpa throws an exception instead of returning null....
  316               ignored.printStackTrace();
  317               return null;
  318           }
  319       }
  320   
  321       private TransactionPolicy startTransaction(String operation, ThreadContext callContext) {
  322           try {
  323               TransactionPolicy txPolicy = createTransactionPolicy(TransactionType.Required, callContext);
  324               return txPolicy;
  325           } catch (Exception e) {
  326               throw new EJBException("Unable to start transaction for " + operation + " operation", e);
  327           }
  328       }
  329   
  330       private void commitTransaction(String operation, ThreadContext callContext, TransactionPolicy txPolicy) {
  331           try {
  332               afterInvoke(txPolicy, callContext);
  333           } catch (Exception e) {
  334               throw new EJBException("Unable to complete transaction for " + operation + " operation", e);
  335           }
  336       }
  337   
  338       private void configureKeyGenerator(CoreDeploymentInfo di) throws OpenEJBException {
  339           if (di.isCmp2()) {
  340               di.setKeyGenerator(new Cmp2KeyGenerator());
  341           } else {
  342               String primaryKeyField = di.getPrimaryKeyField();
  343               Class cmpBeanImpl = di.getCmpImplClass();
  344               if (primaryKeyField != null) {
  345                   di.setKeyGenerator(new SimpleKeyGenerator(cmpBeanImpl, primaryKeyField));
  346               } else if (Object.class.equals(di.getPrimaryKeyClass())) {
  347                   di.setKeyGenerator(new SimpleKeyGenerator(cmpBeanImpl, "OpenEJB_pk"));
  348               } else {
  349                   di.setKeyGenerator(new ComplexKeyGenerator(cmpBeanImpl, di.getPrimaryKeyClass()));
  350               }
  351           }
  352       }
  353   
  354       private class OpenJPALifecycleListener extends AbstractLifecycleListener {
  355   //        protected void eventOccurred(LifecycleEvent event) {
  356   //            int type = event.getType();
  357   //            switch (type) {
  358   //                case LifecycleEvent.BEFORE_PERSIST:
  359   //                    System.out.println("BEFORE_PERSIST");
  360   //                    break;
  361   //                case LifecycleEvent.AFTER_PERSIST:
  362   //                    System.out.println("AFTER_PERSIST");
  363   //                    break;
  364   //                case LifecycleEvent.AFTER_LOAD:
  365   //                    System.out.println("AFTER_LOAD");
  366   //                    break;
  367   //                case LifecycleEvent.BEFORE_STORE:
  368   //                    System.out.println("BEFORE_STORE");
  369   //                    break;
  370   //                case LifecycleEvent.AFTER_STORE:
  371   //                    System.out.println("AFTER_STORE");
  372   //                    break;
  373   //                case LifecycleEvent.BEFORE_CLEAR:
  374   //                    System.out.println("BEFORE_CLEAR");
  375   //                    break;
  376   //                case LifecycleEvent.AFTER_CLEAR:
  377   //                    System.out.println("AFTER_CLEAR");
  378   //                    break;
  379   //                case LifecycleEvent.BEFORE_DELETE:
  380   //                    System.out.println("BEFORE_DELETE");
  381   //                    break;
  382   //                case LifecycleEvent.AFTER_DELETE:
  383   //                    System.out.println("AFTER_DELETE");
  384   //                    break;
  385   //                case LifecycleEvent.BEFORE_DIRTY:
  386   //                    System.out.println("BEFORE_DIRTY");
  387   //                    break;
  388   //                case LifecycleEvent.AFTER_DIRTY:
  389   //                    System.out.println("AFTER_DIRTY");
  390   //                    break;
  391   //                case LifecycleEvent.BEFORE_DIRTY_FLUSHED:
  392   //                    System.out.println("BEFORE_DIRTY_FLUSHED");
  393   //                    break;
  394   //                case LifecycleEvent.AFTER_DIRTY_FLUSHED:
  395   //                    System.out.println("AFTER_DIRTY_FLUSHED");
  396   //                    break;
  397   //                case LifecycleEvent.BEFORE_DETACH:
  398   //                    System.out.println("BEFORE_DETACH");
  399   //                    break;
  400   //                case LifecycleEvent.AFTER_DETACH:
  401   //                    System.out.println("AFTER_DETACH");
  402   //                    break;
  403   //                case LifecycleEvent.BEFORE_ATTACH:
  404   //                    System.out.println("BEFORE_ATTACH");
  405   //                    break;
  406   //                case LifecycleEvent.AFTER_ATTACH:
  407   //                    System.out.println("AFTER_ATTACH");
  408   //                    break;
  409   //                case LifecycleEvent.AFTER_REFRESH:
  410   //                    System.out.println("AFTER_REFRESH");
  411   //                    break;
  412   //                default:
  413   //                    System.out.println("default");
  414   //                    break;
  415   //            }
  416   //            super.eventOccurred(event);
  417   //        }
  418   
  419           public void afterLoad(LifecycleEvent lifecycleEvent) {
  420               eventOccurred(lifecycleEvent);
  421               Object bean = lifecycleEvent.getSource();
  422               // This may seem a bit strange to call ejbActivate immedately followed by ejbLoad,
  423               // but it is completely legal.  Since the ejbActivate method is not allowed to access
  424               // persistent state of the bean (EJB 3.0fr 8.5.2) there should be no concern that the
  425               // call back method clears the bean state before ejbLoad is called.
  426               cmpCallback.setEntityContext((EntityBean) bean);
  427               cmpCallback.ejbActivate((EntityBean) bean);
  428               cmpCallback.ejbLoad((EntityBean) bean);
  429           }
  430   
  431           public void beforeStore(LifecycleEvent lifecycleEvent) {
  432               eventOccurred(lifecycleEvent);
  433               EntityBean bean = (EntityBean) lifecycleEvent.getSource();
  434               if (!creating.get().contains(bean)) {
  435                   cmpCallback.ejbStore(bean);
  436               }
  437           }
  438   
  439           public void afterAttach(LifecycleEvent lifecycleEvent) {
  440               eventOccurred(lifecycleEvent);
  441               Object bean = lifecycleEvent.getSource();
  442               cmpCallback.setEntityContext((EntityBean) bean);
  443           }
  444   
  445           public void beforeDelete(LifecycleEvent lifecycleEvent) {
  446               eventOccurred(lifecycleEvent);
  447               try {
  448                   Object bean = lifecycleEvent.getSource();
  449                   cmpCallback.ejbRemove((EntityBean) bean);
  450               } catch (RemoveException e) {
  451                   throw new PersistenceException(e);
  452               }
  453           }
  454   
  455           public void afterDetach(LifecycleEvent lifecycleEvent) {
  456               eventOccurred(lifecycleEvent);
  457               // todo detach is called after ejbRemove which does not need ejbPassivate
  458               Object bean = lifecycleEvent.getSource();
  459               cmpCallback.ejbPassivate((EntityBean) bean);
  460               cmpCallback.unsetEntityContext((EntityBean) bean);
  461           }
  462   
  463           public void beforePersist(LifecycleEvent lifecycleEvent) {
  464               eventOccurred(lifecycleEvent);
  465           }
  466   
  467           public void afterRefresh(LifecycleEvent lifecycleEvent) {
  468               eventOccurred(lifecycleEvent);
  469           }
  470   
  471           public void beforeDetach(LifecycleEvent lifecycleEvent) {
  472               eventOccurred(lifecycleEvent);
  473           }
  474   
  475           public void beforeAttach(LifecycleEvent lifecycleEvent) {
  476               eventOccurred(lifecycleEvent);
  477           }
  478       }
  479   }

Save This Page
Home » openejb-3.1.2-src » org.apache » openejb » core » cmp » jpa » [javadoc | source]