1 /*
2 * JBoss, Home of Professional Open Source
3 * Copyright 2005, JBoss Inc., and individual contributors as indicated
4 * by the @authors tag. See the copyright.txt in the distribution for a
5 * full listing of individual contributors.
6 *
7 * This is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License as
9 * published by the Free Software Foundation; either version 2.1 of
10 * the License, or (at your option) any later version.
11 *
12 * This software is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this software; if not, write to the Free
19 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
21 */
22 package org.jboss.ejb.plugins.cmp.jdbc;
23
24 import java.lang.reflect.Method;
25 import java.util.ArrayList;
26 import java.util.Collection;
27 import java.util.HashMap;
28 import java.util.Map;
29 import java.util.Set;
30 import java.util.HashSet;
31 import java.util.List;
32 import java.util.Iterator;
33 import java.rmi.RemoteException;
34
35 import javax.ejb.CreateException;
36 import javax.ejb.EJBException;
37 import javax.ejb.FinderException;
38 import javax.ejb.RemoveException;
39 import javax.transaction.Status;
40 import javax.transaction.Transaction;
41 import javax.transaction.TransactionManager;
42
43 import org.jboss.deployment.DeploymentException;
44 import org.jboss.ejb.Container;
45 import org.jboss.ejb.EjbModule;
46 import org.jboss.ejb.EntityContainer;
47 import org.jboss.ejb.EntityEnterpriseContext;
48 import org.jboss.ejb.GenericEntityObjectFactory;
49 import org.jboss.ejb.plugins.cmp.ejbql.Catalog;
50 import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCCMPFieldBridge;
51 import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCCMRFieldBridge;
52 import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCEntityBridge;
53 import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCAbstractEntityBridge;
54 import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCApplicationMetaData;
55 import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCEntityMetaData;
56 import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCXmlFileLoader;
57 import org.jboss.logging.Logger;
58 import org.jboss.metadata.ApplicationMetaData;
59 import org.jboss.tm.TransactionLocal;
60
61 /**
62 * JDBCStoreManager manages storage of persistence data into a table.
63 * Other then loading the initial jbosscmp-jdbc.xml file this class
64 * does very little. The interesting tasks are performed by the command
65 * classes.
66 *
67 * Life-cycle:
68 * Tied to the life-cycle of the entity container.
69 *
70 * Multiplicity:
71 * One per cmp entity bean. This could be less if another implementaion of
72 * EntityPersistenceStore is created and thoes beans use the implementation
73 *
74 * @author <a href="mailto:dain@daingroup.com">Dain Sundstrom</a>
75 * @author <a href="mailto:alex@jboss.org">Alex Loubyansky</a>
76 * @see org.jboss.ejb.EntityPersistenceStore
77 * @version $Revision: 58204 $
78 */
79 public final class JDBCStoreManager implements JDBCEntityPersistenceStore
80 {
81 /** The key used to store the tx data map. */
82 private static final Object TX_DATA_KEY = "TX_DATA_KEY";
83 /** The key to store the Catalog */
84 private static final String CATALOG = "CATALOG";
85
86 private static final String CREATED_MANAGERS = "CREATED_JDBCStoreManagers";
87 private static final String CMP_JDBC = "CMP-JDBC";
88
89 private EjbModule ejbModule;
90 private EntityContainer container;
91 private Logger log;
92
93 private JDBCEntityMetaData metaData;
94 private JDBCEntityBridge entityBridge;
95
96 private JDBCTypeFactory typeFactory;
97 private JDBCQueryManager queryManager;
98
99 private JDBCCommandFactory commandFactory;
100
101 private ReadAheadCache readAheadCache;
102
103 // Manager life cycle commands
104 private JDBCInitCommand initCommand;
105 private JDBCStartCommand startCommand;
106 private JDBCStopCommand stopCommand;
107 private JDBCDestroyCommand destroyCommand;
108
109 // Entity life cycle commands
110 private JDBCCreateBeanClassInstanceCommand createBeanClassInstanceCommand;
111 private JDBCInitEntityCommand initEntityCommand;
112 private JDBCFindEntityCommand findEntityCommand;
113 private JDBCFindEntitiesCommand findEntitiesCommand;
114 private JDBCCreateCommand createEntityCommand;
115 private JDBCPostCreateEntityCommand postCreateEntityCommand;
116 private JDBCRemoveEntityCommand removeEntityCommand;
117 private JDBCLoadEntityCommand loadEntityCommand;
118 private JDBCIsModifiedCommand isModifiedCommand;
119 private JDBCStoreEntityCommand storeEntityCommand;
120 private JDBCActivateEntityCommand activateEntityCommand;
121 private JDBCPassivateEntityCommand passivateEntityCommand;
122
123 // commands
124 private JDBCLoadRelationCommand loadRelationCommand;
125 private JDBCDeleteRelationsCommand deleteRelationsCommand;
126 private JDBCInsertRelationsCommand insertRelationsCommand;
127
128 /** A Transaction manager so that we can link preloaded data to a transaction */
129 private TransactionManager tm;
130 private TransactionLocal txDataMap;
131
132 /** Set of EJBLocalObject instances to be cascade-deleted excluding those that should be batch-cascade-deleted. */
133 private TransactionLocal cascadeDeleteSet = new TransactionLocal()
134 {
135 protected Object initialValue()
136 {
137 return new CascadeDeleteRegistry();
138 }
139 };
140
141 /**
142 * Gets the container for this entity.
143 * @return the container for this entity; null if container has not been set
144 */
145 public EntityContainer getContainer()
146 {
147 return container;
148 }
149
150 /**
151 * Sets the container for this entity.
152 * @param container the container for this entity
153 * @throws ClassCastException if the container is not an instance of
154 * EntityContainer
155 */
156 public void setContainer(Container container)
157 {
158 this.container = (EntityContainer)container;
159 if(container != null)
160 {
161 ejbModule = container.getEjbModule();
162 log = Logger.getLogger(
163 this.getClass().getName() +
164 "." +
165 container.getBeanMetaData().getEjbName());
166 }
167 else
168 {
169 ejbModule = null;
170 }
171 }
172
173 public JDBCAbstractEntityBridge getEntityBridge()
174 {
175 return entityBridge;
176 }
177
178 public JDBCTypeFactory getJDBCTypeFactory()
179 {
180 return typeFactory;
181 }
182
183 public JDBCEntityMetaData getMetaData()
184 {
185 return metaData;
186 }
187
188 public JDBCQueryManager getQueryManager()
189 {
190 return queryManager;
191 }
192
193 public JDBCCommandFactory getCommandFactory()
194 {
195 return commandFactory;
196 }
197
198 public ReadAheadCache getReadAheadCache()
199 {
200 return readAheadCache;
201 }
202
203 //
204 // Genertic data containers
205 //
206 public Map getApplicationDataMap()
207 {
208 return ejbModule.getModuleDataMap();
209 }
210
211 public Object getApplicationData(Object key)
212 {
213 return ejbModule.getModuleData(key);
214 }
215
216 public void putApplicationData(Object key, Object value)
217 {
218 ejbModule.putModuleData(key, value);
219 }
220
221 private Map getApplicationTxDataMap()
222 {
223 try
224 {
225 Transaction tx = tm.getTransaction();
226 if(tx == null)
227 {
228 return null;
229 }
230
231 // get the txDataMap from the txMap
232 Map txMap = (Map)txDataMap.get(tx);
233
234 // do we have an existing map
235 if(txMap == null)
236 {
237 int status = tx.getStatus();
238 if(status == Status.STATUS_ACTIVE || status == Status.STATUS_PREPARING)
239 {
240 // create and add the new map
241 txMap = new HashMap();
242 txDataMap.set(tx, txMap);
243 }
244 }
245 return txMap;
246 }
247 catch(EJBException e)
248 {
249 throw e;
250 }
251 catch(Exception e)
252 {
253 throw new EJBException("Error getting application tx data map.", e);
254 }
255 }
256
257 /**
258 * Schedules instances for cascade-delete
259 */
260 public void scheduleCascadeDelete(List pks)
261 {
262 CascadeDeleteRegistry registry = (CascadeDeleteRegistry)cascadeDeleteSet.get();
263 registry.scheduleAll(pks);
264 }
265
266 /**
267 * Unschedules instance cascade delete.
268 * @param pk instance primary key.
269 * @return true if the instance was scheduled for cascade deleted.
270 */
271 public boolean unscheduledCascadeDelete(Object pk)
272 {
273 CascadeDeleteRegistry registry = (CascadeDeleteRegistry)cascadeDeleteSet.get();
274 return registry.unschedule(pk);
275 }
276
277 public Object getApplicationTxData(Object key)
278 {
279 Map map = getApplicationTxDataMap();
280 if(map != null)
281 {
282 return map.get(key);
283 }
284 return null;
285 }
286
287 public void putApplicationTxData(Object key, Object value)
288 {
289 Map map = getApplicationTxDataMap();
290 if(map != null)
291 {
292 map.put(key, value);
293 }
294 }
295
296 private Map getEntityTxDataMap()
297 {
298 Map entityTxDataMap = (Map)getApplicationTxData(this);
299 if(entityTxDataMap == null)
300 {
301 entityTxDataMap = new HashMap();
302 putApplicationTxData(this, entityTxDataMap);
303 }
304 return entityTxDataMap;
305 }
306
307 public Object getEntityTxData(Object key)
308 {
309 return getEntityTxDataMap().get(key);
310 }
311
312 public void putEntityTxData(Object key, Object value)
313 {
314 getEntityTxDataMap().put(key, value);
315 }
316
317 public void removeEntityTxData(Object key)
318 {
319 getEntityTxDataMap().remove(key);
320 }
321
322 public Catalog getCatalog()
323 {
324 return (Catalog)getApplicationData(CATALOG);
325 }
326
327 private void initApplicationDataMap()
328 {
329 Map moduleData = ejbModule.getModuleDataMap();
330 synchronized(moduleData)
331 {
332 txDataMap = (TransactionLocal)moduleData.get(TX_DATA_KEY);
333 if(txDataMap == null)
334 {
335 txDataMap = new TransactionLocal();
336 moduleData.put(TX_DATA_KEY, txDataMap);
337 }
338 }
339 }
340
341 /**
342 * Does almost nothing because other services such
343 * as JDBC data sources may not have been started.
344 */
345 public void create() throws Exception
346 {
347 // Store a reference to this manager in an application level hashtable.
348 // This way in the start method other managers will be able to know
349 // the other managers.
350 HashMap managersMap = (HashMap)getApplicationData(CREATED_MANAGERS);
351 if(managersMap == null)
352 {
353 managersMap = new HashMap();
354 putApplicationData(CREATED_MANAGERS, managersMap);
355 }
356 managersMap.put(container.getBeanMetaData().getEjbName(), this);
357 }
358
359 /**
360 * Bring the store to a fully initialized state
361 */
362 public void start() throws Exception
363 {
364 //
365 //
366 // Start Phase 1: create bridge and commands but
367 // don't access other entities
368 initStoreManager();
369
370
371 // If all managers have been started (this is the last manager),
372 // complete the other two phases of startup.
373 Catalog catalog = getCatalog();
374 HashMap managersMap = (HashMap)getApplicationData(CREATED_MANAGERS);
375 if(catalog.getEntityCount() == managersMap.size()
376 && catalog.getEJBNames().equals(managersMap.keySet()))
377 {
378 // Make a copy of the managers (for safty)
379 ArrayList managers = new ArrayList(managersMap.values());
380
381 //
382 //
383 // Start Phase 2: resolve relationships
384 for(int i = 0; i < managers.size(); ++i)
385 {
386 JDBCStoreManager manager = (JDBCStoreManager)managers.get(i);
387 manager.resolveRelationships();
388 }
389
390 //
391 //
392 // Start Phase 3: create tables and compile queries
393 for(int i = 0; i < managers.size(); ++i)
394 {
395 JDBCStoreManager manager = (JDBCStoreManager)managers.get(i);
396 manager.startStoreManager();
397 }
398
399 // add foreign key constraints
400 for(int i = 0; i < managers.size(); ++i)
401 {
402 JDBCStoreManager manager = (JDBCStoreManager)managers.get(i);
403 manager.startCommand.addForeignKeyConstraints();
404 }
405 }
406 }
407
408 /**
409 * Preforms as much initialization as possible without referencing
410 * another entity.
411 */
412 private void initStoreManager() throws Exception
413 {
414 if(log.isDebugEnabled())
415 log.debug("Initializing CMP plugin for " + container.getBeanMetaData().getEjbName());
416
417 // get the transaction manager
418 tm = container.getTransactionManager();
419
420 // initializes the generic data containers
421 initApplicationDataMap();
422
423 // load the metadata for this entity
424 metaData = loadJDBCEntityMetaData();
425
426 // setup the type factory, which is used to map java types to sql types.
427 typeFactory = new JDBCTypeFactory(
428 metaData.getTypeMapping(),
429 metaData.getJDBCApplication().getValueClasses(),
430 metaData.getJDBCApplication().getUserTypeMappings()
431 );
432
433 // create the bridge between java land and this engine (sql land)
434 entityBridge = new JDBCEntityBridge(metaData, this);
435 entityBridge.init();
436
437 // add the entity bridge to the catalog
438 Catalog catalog = getCatalog();
439 if(catalog == null)
440 {
441 catalog = new Catalog();
442 putApplicationData(CATALOG, catalog);
443 }
444 catalog.addEntity(entityBridge);
445
446 // create the read ahead cache
447 readAheadCache = new ReadAheadCache(this);
448 readAheadCache.create();
449
450 // Set up Commands
451 commandFactory = new JDBCCommandFactory(this);
452
453 // Execute the init command
454 initCommand = commandFactory.createInitCommand();
455 initCommand.execute();
456 }
457
458 private void resolveRelationships() throws Exception
459 {
460 entityBridge.resolveRelationships();
461 }
462
463 /**
464 * Brings the store manager into a completely running state.
465 * This method will create the database table and compile the queries.
466 */
467 private void startStoreManager() throws Exception
468 {
469 entityBridge.start();
470
471 // Store manager life cycle commands
472 startCommand = commandFactory.createStartCommand();
473 stopCommand = commandFactory.createStopCommand();
474 destroyCommand = commandFactory.createDestroyCommand();
475
476 // Entity commands
477 initEntityCommand = commandFactory.createInitEntityCommand();
478 createBeanClassInstanceCommand = commandFactory.createCreateBeanClassInstanceCommand();
479 findEntityCommand = commandFactory.createFindEntityCommand();
480 findEntitiesCommand = commandFactory.createFindEntitiesCommand();
481 createEntityCommand = commandFactory.createCreateEntityCommand();
482 postCreateEntityCommand = commandFactory.createPostCreateEntityCommand();
483 removeEntityCommand = commandFactory.createRemoveEntityCommand();
484 loadEntityCommand = commandFactory.createLoadEntityCommand();
485 isModifiedCommand = commandFactory.createIsModifiedCommand();
486 storeEntityCommand = commandFactory.createStoreEntityCommand();
487 activateEntityCommand = commandFactory.createActivateEntityCommand();
488 passivateEntityCommand = commandFactory.createPassivateEntityCommand();
489
490 // Relation commands
491 loadRelationCommand = commandFactory.createLoadRelationCommand();
492 deleteRelationsCommand = commandFactory.createDeleteRelationsCommand();
493 insertRelationsCommand = commandFactory.createInsertRelationsCommand();
494
495 // Create the query manager
496 queryManager = new JDBCQueryManager(this);
497
498 // Execute the start command, creates the tables
499 startCommand.execute();
500
501 // Start the query manager. At this point is creates all of the
502 // query commands. The must occure in the start phase, as
503 // queries can opperate on other entities in the application, and
504 // all entities are gaurenteed to be createed until the start phase.
505 queryManager.start();
506
507 readAheadCache.start();
508 }
509
510 public void stop()
511 {
512 // On deploy errors, sometimes CMPStoreManager was never initialized!
513 if(stopCommand != null)
514 {
515 Map managersMap = (HashMap)getApplicationData(CREATED_MANAGERS);
516 while(!managersMap.isEmpty())
517 {
518 int stoppedInIteration = 0;
519 for(Iterator i = managersMap.values().iterator(); i.hasNext();)
520 {
521 JDBCStoreManager manager = (JDBCStoreManager)i.next();
522 if(manager.stopCommand == null || manager.stopCommand.execute())
523 {
524 i.remove();
525 ++stoppedInIteration;
526 }
527 }
528
529 if(stoppedInIteration == 0)
530 {
531 break;
532 }
533 }
534 }
535 readAheadCache.stop();
536 }
537
538 public void destroy()
539 {
540 // On deploy errors, sometimes CMPStoreManager was never initialized!
541 if(destroyCommand != null)
542 {
543 destroyCommand.execute();
544 }
545
546 if(readAheadCache != null)
547 {
548 readAheadCache.destroy();
549 }
550
551 readAheadCache = null;
552 if(queryManager != null)
553 {
554 queryManager.clear();
555 }
556 queryManager = null;
557 //Remove proxy from proxy map so UnifiedClassloader may be released
558 if(createBeanClassInstanceCommand != null)
559 {
560 createBeanClassInstanceCommand.destroy();
561 } // end of if ()
562 }
563
564 //
565 // EJB Life Cycle Commands
566 //
567 /**
568 * Returns a new instance of a class which implemnts the bean class.
569 *
570 * @return the new instance
571 */
572 public Object createBeanClassInstance() throws Exception
573 {
574 if(createBeanClassInstanceCommand == null)
575 throw new IllegalStateException("createBeanClassInstanceCommand == null");
576 return createBeanClassInstanceCommand.execute();
577 }
578
579 public void initEntity(EntityEnterpriseContext ctx)
580 {
581 initEntityCommand.execute(ctx);
582 }
583
584 public Object createEntity(Method createMethod, Object[] args, EntityEnterpriseContext ctx)
585 throws CreateException
586 {
587 Object pk = createEntityCommand.execute(createMethod, args, ctx);
588 if(pk == null)
589 throw new CreateException("Primary key for created instance is null.");
590 return pk;
591 }
592
593 public Object postCreateEntity(Method createMethod, Object[] args, EntityEnterpriseContext ctx)
594 {
595 return postCreateEntityCommand.execute(createMethod, args, ctx);
596 }
597
598 public Object findEntity(Method finderMethod,
599 Object[] args,
600 EntityEnterpriseContext ctx,
601 GenericEntityObjectFactory factory)
602 throws FinderException
603 {
604 return findEntityCommand.execute(finderMethod, args, ctx, factory);
605 }
606
607 public Collection findEntities(Method finderMethod,
608 Object[] args,
609 EntityEnterpriseContext ctx,
610 GenericEntityObjectFactory factory)
611 throws FinderException
612 {
613 return findEntitiesCommand.execute(finderMethod, args, ctx, factory);
614 }
615
616 public void activateEntity(EntityEnterpriseContext ctx)
617 {
618 activateEntityCommand.execute(ctx);
619 }
620
621 /**
622 * Loads entity.
623 * If entity not found NoSuchEntityException is thrown.
624 * @param ctx - entity context.
625 */
626 public void loadEntity(EntityEnterpriseContext ctx)
627 {
628 loadEntity(ctx, true);
629 }
630
631 public boolean loadEntity(EntityEnterpriseContext ctx, boolean failIfNotFound)
632 {
633 // is any on the data already in the entity valid
634 if(!ctx.isValid())
635 {
636 if(log.isTraceEnabled())
637 {
638 log.trace("RESET PERSISTENCE CONTEXT: id=" + ctx.getId());
639 }
640 entityBridge.resetPersistenceContext(ctx);
641 }
642
643 // mark the entity as created; if it was loading it was created
644 JDBCEntityBridge.setCreated(ctx);
645
646 return loadEntityCommand.execute(ctx, failIfNotFound);
647 }
648
649 public void loadField(JDBCCMPFieldBridge field, EntityEnterpriseContext ctx)
650 {
651 loadEntityCommand.execute(field, ctx);
652 }
653
654 public boolean isStoreRequired(EntityEnterpriseContext ctx)
655 {
656 return isModifiedCommand.execute(ctx);
657 }
658
659 public boolean isModified(EntityEnterpriseContext ctx)
660 {
661 return entityBridge.isModified(ctx);
662 }
663
664 public void storeEntity(EntityEnterpriseContext ctx)
665 {
666 storeEntityCommand.execute(ctx);
667 synchronizeRelationData();
668 }
669
670 private void synchronizeRelationData()
671 {
672 final JDBCCMRFieldBridge[] cmrFields = (JDBCCMRFieldBridge[]) entityBridge.getCMRFields();
673 for(int i = 0; i < cmrFields.length; ++i)
674 {
675 final JDBCCMRFieldBridge.RelationDataManager relationManager = cmrFields[i].getRelationDataManager();
676 if(relationManager.isDirty())
677 {
678 final RelationData relationData = relationManager.getRelationData();
679
680 deleteRelations(relationData);
681 insertRelations(relationData);
682
683 relationData.addedRelations.clear();
684 relationData.removedRelations.clear();
685 relationData.notRelatedPairs.clear();
686 }
687 }
688 }
689
690 public void passivateEntity(EntityEnterpriseContext ctx)
691 {
692 passivateEntityCommand.execute(ctx);
693 }
694
695 public void removeEntity(EntityEnterpriseContext ctx) throws RemoveException, RemoteException
696 {
697 removeEntityCommand.execute(ctx);
698 }
699
700 //
701 // Relationship Commands
702 //
703 public Collection loadRelation(JDBCCMRFieldBridge cmrField, Object pk)
704 {
705 return loadRelationCommand.execute(cmrField, pk);
706 }
707
708 private void deleteRelations(RelationData relationData)
709 {
710 deleteRelationsCommand.execute(relationData);
711 }
712
713 private void insertRelations(RelationData relationData)
714 {
715 insertRelationsCommand.execute(relationData);
716 }
717
718 private JDBCEntityMetaData loadJDBCEntityMetaData()
719 throws DeploymentException
720 {
721 ApplicationMetaData amd = container.getBeanMetaData().getApplicationMetaData();
722
723 // Get JDBC MetaData
724 JDBCApplicationMetaData jamd = (JDBCApplicationMetaData)amd.getPluginData(CMP_JDBC);
725
726 if(jamd == null)
727 {
728 // we are the first cmp entity to need jbosscmp-jdbc.
729 // Load jbosscmp-jdbc.xml for the whole application
730 JDBCXmlFileLoader jfl = new JDBCXmlFileLoader(container, log);
731
732 jamd = jfl.load();
733 amd.addPluginData(CMP_JDBC, jamd);
734 }
735
736 // Get JDBC Bean MetaData
737 String ejbName = container.getBeanMetaData().getEjbName();
738 JDBCEntityMetaData metadata = jamd.getBeanByEjbName(ejbName);
739 if(metadata == null)
740 {
741 throw new DeploymentException("No metadata found for bean " + ejbName);
742 }
743 return metadata;
744 }
745
746 // Inner
747
748 private final class CascadeDeleteRegistry
749 {
750 private Set scheduled;
751
752 public void scheduleAll(List pks)
753 {
754 if(scheduled == null)
755 {
756 scheduled = new HashSet();
757 }
758 scheduled.addAll(pks);
759 }
760
761 public boolean unschedule(Object pk)
762 {
763 return scheduled.remove(pk);
764 }
765 }
766 }