001: /*
002: * JBoss, Home of Professional Open Source.
003: * Copyright 2006, Red Hat Middleware LLC, and individual contributors
004: * as indicated by the @author tags. See the copyright.txt file in the
005: * distribution for a full listing of individual contributors.
006: *
007: * This is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU Lesser General Public License as
009: * published by the Free Software Foundation; either version 2.1 of
010: * the License, or (at your option) any later version.
011: *
012: * This software is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this software; if not, write to the Free
019: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021: */
022: package org.jboss.ejb.plugins.cmp.jdbc;
023:
024: import java.lang.reflect.Method;
025: import java.util.ArrayList;
026: import java.util.Collection;
027: import java.util.HashMap;
028: import java.util.Map;
029: import java.util.Set;
030: import java.util.HashSet;
031: import java.util.List;
032: import java.util.Iterator;
033: import java.rmi.RemoteException;
034:
035: import javax.ejb.CreateException;
036: import javax.ejb.EJBException;
037: import javax.ejb.FinderException;
038: import javax.ejb.RemoveException;
039: import javax.transaction.Status;
040: import javax.transaction.Transaction;
041: import javax.transaction.TransactionManager;
042:
043: import org.jboss.deployment.DeploymentException;
044: import org.jboss.ejb.Container;
045: import org.jboss.ejb.EjbModule;
046: import org.jboss.ejb.EntityContainer;
047: import org.jboss.ejb.EntityEnterpriseContext;
048: import org.jboss.ejb.GenericEntityObjectFactory;
049: import org.jboss.ejb.plugins.cmp.ejbql.Catalog;
050: import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCCMPFieldBridge;
051: import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCCMRFieldBridge;
052: import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCEntityBridge;
053: import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCAbstractEntityBridge;
054: import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCApplicationMetaData;
055: import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCEntityMetaData;
056: import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCXmlFileLoader;
057: import org.jboss.logging.Logger;
058: import org.jboss.metadata.ApplicationMetaData;
059: import org.jboss.tm.TransactionLocal;
060:
061: /**
062: * JDBCStoreManager manages storage of persistence data into a table.
063: * Other then loading the initial jbosscmp-jdbc.xml file this class
064: * does very little. The interesting tasks are performed by the command
065: * classes.
066: *
067: * Life-cycle:
068: * Tied to the life-cycle of the entity container.
069: *
070: * Multiplicity:
071: * One per cmp entity bean. This could be less if another implementaion of
072: * EntityPersistenceStore is created and thoes beans use the implementation
073: *
074: * @author <a href="mailto:dain@daingroup.com">Dain Sundstrom</a>
075: * @author <a href="mailto:alex@jboss.org">Alex Loubyansky</a>
076: * @see org.jboss.ejb.EntityPersistenceStore
077: * @version $Revision: 57209 $
078: */
079: public final class JDBCStoreManager implements
080: JDBCEntityPersistenceStore {
081: /** The key used to store the tx data map. */
082: private static final Object TX_DATA_KEY = "TX_DATA_KEY";
083: /** The key to store the Catalog */
084: private static final String CATALOG = "CATALOG";
085:
086: private static final String CREATED_MANAGERS = "CREATED_JDBCStoreManagers";
087: private static final String CMP_JDBC = "CMP-JDBC";
088:
089: private EjbModule ejbModule;
090: private EntityContainer container;
091: private Logger log;
092:
093: private JDBCEntityMetaData metaData;
094: private JDBCEntityBridge entityBridge;
095:
096: private JDBCTypeFactory typeFactory;
097: private JDBCQueryManager queryManager;
098:
099: 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: protected Object initialValue() {
135: return new CascadeDeleteRegistry();
136: }
137: };
138:
139: /**
140: * Gets the container for this entity.
141: * @return the container for this entity; null if container has not been set
142: */
143: public EntityContainer getContainer() {
144: return container;
145: }
146:
147: /**
148: * Sets the container for this entity.
149: * @param container the container for this entity
150: * @throws ClassCastException if the container is not an instance of
151: * EntityContainer
152: */
153: public void setContainer(Container container) {
154: this .container = (EntityContainer) container;
155: if (container != null) {
156: ejbModule = container.getEjbModule();
157: log = Logger.getLogger(this .getClass().getName() + "."
158: + container.getBeanMetaData().getEjbName());
159: } else {
160: ejbModule = null;
161: }
162: }
163:
164: public JDBCAbstractEntityBridge getEntityBridge() {
165: return entityBridge;
166: }
167:
168: public JDBCTypeFactory getJDBCTypeFactory() {
169: return typeFactory;
170: }
171:
172: public JDBCEntityMetaData getMetaData() {
173: return metaData;
174: }
175:
176: public JDBCQueryManager getQueryManager() {
177: return queryManager;
178: }
179:
180: public JDBCCommandFactory getCommandFactory() {
181: return commandFactory;
182: }
183:
184: public ReadAheadCache getReadAheadCache() {
185: return readAheadCache;
186: }
187:
188: //
189: // Genertic data containers
190: //
191: public Map getApplicationDataMap() {
192: return ejbModule.getModuleDataMap();
193: }
194:
195: public Object getApplicationData(Object key) {
196: return ejbModule.getModuleData(key);
197: }
198:
199: public void putApplicationData(Object key, Object value) {
200: ejbModule.putModuleData(key, value);
201: }
202:
203: private Map getApplicationTxDataMap() {
204: try {
205: Transaction tx = tm.getTransaction();
206: if (tx == null) {
207: return null;
208: }
209:
210: // get the txDataMap from the txMap
211: Map txMap = (Map) txDataMap.get(tx);
212:
213: // do we have an existing map
214: if (txMap == null) {
215: int status = tx.getStatus();
216: if (status == Status.STATUS_ACTIVE
217: || status == Status.STATUS_PREPARING) {
218: // create and add the new map
219: txMap = new HashMap();
220: txDataMap.set(tx, txMap);
221: }
222: }
223: return txMap;
224: } catch (EJBException e) {
225: throw e;
226: } catch (Exception e) {
227: throw new EJBException(
228: "Error getting application tx data map.", e);
229: }
230: }
231:
232: /**
233: * Schedules instances for cascade-delete
234: */
235: public void scheduleCascadeDelete(List pks) {
236: CascadeDeleteRegistry registry = (CascadeDeleteRegistry) cascadeDeleteSet
237: .get();
238: registry.scheduleAll(pks);
239: }
240:
241: /**
242: * Unschedules instance cascade delete.
243: * @param pk instance primary key.
244: * @return true if the instance was scheduled for cascade deleted.
245: */
246: public boolean unscheduledCascadeDelete(Object pk) {
247: CascadeDeleteRegistry registry = (CascadeDeleteRegistry) cascadeDeleteSet
248: .get();
249: return registry.unschedule(pk);
250: }
251:
252: public Object getApplicationTxData(Object key) {
253: Map map = getApplicationTxDataMap();
254: if (map != null) {
255: return map.get(key);
256: }
257: return null;
258: }
259:
260: public void putApplicationTxData(Object key, Object value) {
261: Map map = getApplicationTxDataMap();
262: if (map != null) {
263: map.put(key, value);
264: }
265: }
266:
267: private Map getEntityTxDataMap() {
268: Map entityTxDataMap = (Map) getApplicationTxData(this );
269: if (entityTxDataMap == null) {
270: entityTxDataMap = new HashMap();
271: putApplicationTxData(this , entityTxDataMap);
272: }
273: return entityTxDataMap;
274: }
275:
276: public Object getEntityTxData(Object key) {
277: return getEntityTxDataMap().get(key);
278: }
279:
280: public void putEntityTxData(Object key, Object value) {
281: getEntityTxDataMap().put(key, value);
282: }
283:
284: public void removeEntityTxData(Object key) {
285: getEntityTxDataMap().remove(key);
286: }
287:
288: public Catalog getCatalog() {
289: return (Catalog) getApplicationData(CATALOG);
290: }
291:
292: private void initApplicationDataMap() {
293: Map moduleData = ejbModule.getModuleDataMap();
294: synchronized (moduleData) {
295: txDataMap = (TransactionLocal) moduleData.get(TX_DATA_KEY);
296: if (txDataMap == null) {
297: txDataMap = new TransactionLocal();
298: moduleData.put(TX_DATA_KEY, txDataMap);
299: }
300: }
301: }
302:
303: /**
304: * Does almost nothing because other services such
305: * as JDBC data sources may not have been started.
306: */
307: public void create() throws Exception {
308: // Store a reference to this manager in an application level hashtable.
309: // This way in the start method other managers will be able to know
310: // the other managers.
311: HashMap managersMap = (HashMap) getApplicationData(CREATED_MANAGERS);
312: if (managersMap == null) {
313: managersMap = new HashMap();
314: putApplicationData(CREATED_MANAGERS, managersMap);
315: }
316: managersMap.put(container.getBeanMetaData().getEjbName(), this );
317: }
318:
319: /**
320: * Bring the store to a fully initialized state
321: */
322: public void start() throws Exception {
323: //
324: //
325: // Start Phase 1: create bridge and commands but
326: // don't access other entities
327: initStoreManager();
328:
329: // If all managers have been started (this is the last manager),
330: // complete the other two phases of startup.
331: Catalog catalog = getCatalog();
332: HashMap managersMap = (HashMap) getApplicationData(CREATED_MANAGERS);
333: if (catalog.getEntityCount() == managersMap.size()
334: && catalog.getEJBNames().equals(managersMap.keySet())) {
335: // Make a copy of the managers (for safty)
336: ArrayList managers = new ArrayList(managersMap.values());
337:
338: //
339: //
340: // Start Phase 2: resolve relationships
341: for (int i = 0; i < managers.size(); ++i) {
342: JDBCStoreManager manager = (JDBCStoreManager) managers
343: .get(i);
344: manager.resolveRelationships();
345: }
346:
347: //
348: //
349: // Start Phase 3: create tables and compile queries
350: for (int i = 0; i < managers.size(); ++i) {
351: JDBCStoreManager manager = (JDBCStoreManager) managers
352: .get(i);
353: manager.startStoreManager();
354: }
355:
356: // add foreign key constraints
357: for (int i = 0; i < managers.size(); ++i) {
358: JDBCStoreManager manager = (JDBCStoreManager) managers
359: .get(i);
360: manager.startCommand.addForeignKeyConstraints();
361: }
362: }
363: }
364:
365: /**
366: * Preforms as much initialization as possible without referencing
367: * another entity.
368: */
369: private void initStoreManager() throws Exception {
370: if (log.isDebugEnabled())
371: log.debug("Initializing CMP plugin for "
372: + container.getBeanMetaData().getEjbName());
373:
374: // get the transaction manager
375: tm = container.getTransactionManager();
376:
377: // initializes the generic data containers
378: initApplicationDataMap();
379:
380: // load the metadata for this entity
381: metaData = loadJDBCEntityMetaData();
382:
383: // setup the type factory, which is used to map java types to sql types.
384: typeFactory = new JDBCTypeFactory(metaData.getTypeMapping(),
385: metaData.getJDBCApplication().getValueClasses(),
386: metaData.getJDBCApplication().getUserTypeMappings());
387:
388: // create the bridge between java land and this engine (sql land)
389: entityBridge = new JDBCEntityBridge(metaData, this );
390: entityBridge.init();
391:
392: // add the entity bridge to the catalog
393: Catalog catalog = getCatalog();
394: if (catalog == null) {
395: catalog = new Catalog();
396: putApplicationData(CATALOG, catalog);
397: }
398: catalog.addEntity(entityBridge);
399:
400: // create the read ahead cache
401: readAheadCache = new ReadAheadCache(this );
402: readAheadCache.create();
403:
404: // Set up Commands
405: commandFactory = new JDBCCommandFactory(this );
406:
407: // Execute the init command
408: initCommand = commandFactory.createInitCommand();
409: initCommand.execute();
410: }
411:
412: private void resolveRelationships() throws Exception {
413: entityBridge.resolveRelationships();
414: }
415:
416: /**
417: * Brings the store manager into a completely running state.
418: * This method will create the database table and compile the queries.
419: */
420: private void startStoreManager() throws Exception {
421: entityBridge.start();
422:
423: // Store manager life cycle commands
424: startCommand = commandFactory.createStartCommand();
425: stopCommand = commandFactory.createStopCommand();
426: destroyCommand = commandFactory.createDestroyCommand();
427:
428: // Entity commands
429: initEntityCommand = commandFactory.createInitEntityCommand();
430: createBeanClassInstanceCommand = commandFactory
431: .createCreateBeanClassInstanceCommand();
432: findEntityCommand = commandFactory.createFindEntityCommand();
433: findEntitiesCommand = commandFactory
434: .createFindEntitiesCommand();
435: createEntityCommand = commandFactory
436: .createCreateEntityCommand();
437: postCreateEntityCommand = commandFactory
438: .createPostCreateEntityCommand();
439: removeEntityCommand = commandFactory
440: .createRemoveEntityCommand();
441: loadEntityCommand = commandFactory.createLoadEntityCommand();
442: isModifiedCommand = commandFactory.createIsModifiedCommand();
443: storeEntityCommand = commandFactory.createStoreEntityCommand();
444: activateEntityCommand = commandFactory
445: .createActivateEntityCommand();
446: passivateEntityCommand = commandFactory
447: .createPassivateEntityCommand();
448:
449: // Relation commands
450: loadRelationCommand = commandFactory
451: .createLoadRelationCommand();
452: deleteRelationsCommand = commandFactory
453: .createDeleteRelationsCommand();
454: insertRelationsCommand = commandFactory
455: .createInsertRelationsCommand();
456:
457: // Create the query manager
458: queryManager = new JDBCQueryManager(this );
459:
460: // Execute the start command, creates the tables
461: startCommand.execute();
462:
463: // Start the query manager. At this point is creates all of the
464: // query commands. The must occure in the start phase, as
465: // queries can opperate on other entities in the application, and
466: // all entities are gaurenteed to be createed until the start phase.
467: queryManager.start();
468:
469: readAheadCache.start();
470: }
471:
472: public void stop() {
473: // On deploy errors, sometimes CMPStoreManager was never initialized!
474: if (stopCommand != null) {
475: Map managersMap = (HashMap) getApplicationData(CREATED_MANAGERS);
476: while (!managersMap.isEmpty()) {
477: int stoppedInIteration = 0;
478: for (Iterator i = managersMap.values().iterator(); i
479: .hasNext();) {
480: JDBCStoreManager manager = (JDBCStoreManager) i
481: .next();
482: if (manager.stopCommand == null
483: || manager.stopCommand.execute()) {
484: i.remove();
485: ++stoppedInIteration;
486: }
487: }
488:
489: if (stoppedInIteration == 0) {
490: break;
491: }
492: }
493: }
494: readAheadCache.stop();
495: }
496:
497: public void destroy() {
498: // On deploy errors, sometimes CMPStoreManager was never initialized!
499: if (destroyCommand != null) {
500: destroyCommand.execute();
501: }
502:
503: if (readAheadCache != null) {
504: readAheadCache.destroy();
505: }
506:
507: readAheadCache = null;
508: if (queryManager != null) {
509: queryManager.clear();
510: }
511: queryManager = null;
512: //Remove proxy from proxy map so UnifiedClassloader may be released
513: if (createBeanClassInstanceCommand != null) {
514: createBeanClassInstanceCommand.destroy();
515: } // end of if ()
516: }
517:
518: //
519: // EJB Life Cycle Commands
520: //
521: /**
522: * Returns a new instance of a class which implemnts the bean class.
523: *
524: * @return the new instance
525: */
526: public Object createBeanClassInstance() throws Exception {
527: if (createBeanClassInstanceCommand == null)
528: throw new IllegalStateException(
529: "createBeanClassInstanceCommand == null");
530: return createBeanClassInstanceCommand.execute();
531: }
532:
533: public void initEntity(EntityEnterpriseContext ctx) {
534: initEntityCommand.execute(ctx);
535: }
536:
537: public Object createEntity(Method createMethod, Object[] args,
538: EntityEnterpriseContext ctx) throws CreateException {
539: Object pk = createEntityCommand
540: .execute(createMethod, args, ctx);
541: if (pk == null)
542: throw new CreateException(
543: "Primary key for created instance is null.");
544: return pk;
545: }
546:
547: public Object postCreateEntity(Method createMethod, Object[] args,
548: EntityEnterpriseContext ctx) {
549: return postCreateEntityCommand.execute(createMethod, args, ctx);
550: }
551:
552: public Object findEntity(Method finderMethod, Object[] args,
553: EntityEnterpriseContext ctx,
554: GenericEntityObjectFactory factory) throws FinderException {
555: return findEntityCommand.execute(finderMethod, args, ctx,
556: factory);
557: }
558:
559: public Collection findEntities(Method finderMethod, Object[] args,
560: EntityEnterpriseContext ctx,
561: GenericEntityObjectFactory factory) throws FinderException {
562: return findEntitiesCommand.execute(finderMethod, args, ctx,
563: factory);
564: }
565:
566: public void activateEntity(EntityEnterpriseContext ctx) {
567: activateEntityCommand.execute(ctx);
568: }
569:
570: /**
571: * Loads entity.
572: * If entity not found NoSuchEntityException is thrown.
573: * @param ctx - entity context.
574: */
575: public void loadEntity(EntityEnterpriseContext ctx) {
576: loadEntity(ctx, true);
577: }
578:
579: public boolean loadEntity(EntityEnterpriseContext ctx,
580: boolean failIfNotFound) {
581: // is any on the data already in the entity valid
582: if (!ctx.isValid()) {
583: if (log.isTraceEnabled()) {
584: log.trace("RESET PERSISTENCE CONTEXT: id="
585: + ctx.getId());
586: }
587: entityBridge.resetPersistenceContext(ctx);
588: }
589:
590: // mark the entity as created; if it was loading it was created
591: JDBCEntityBridge.setCreated(ctx);
592:
593: return loadEntityCommand.execute(ctx, failIfNotFound);
594: }
595:
596: public void loadField(JDBCCMPFieldBridge field,
597: EntityEnterpriseContext ctx) {
598: loadEntityCommand.execute(field, ctx);
599: }
600:
601: public boolean isStoreRequired(EntityEnterpriseContext ctx) {
602: return isModifiedCommand.execute(ctx);
603: }
604:
605: public boolean isModified(EntityEnterpriseContext ctx) {
606: return entityBridge.isModified(ctx);
607: }
608:
609: public void storeEntity(EntityEnterpriseContext ctx) {
610: storeEntityCommand.execute(ctx);
611: synchronizeRelationData();
612: }
613:
614: private void synchronizeRelationData() {
615: final JDBCCMRFieldBridge[] cmrFields = (JDBCCMRFieldBridge[]) entityBridge
616: .getCMRFields();
617: for (int i = 0; i < cmrFields.length; ++i) {
618: final JDBCCMRFieldBridge.RelationDataManager relationManager = cmrFields[i]
619: .getRelationDataManager();
620: if (relationManager.isDirty()) {
621: final RelationData relationData = relationManager
622: .getRelationData();
623:
624: deleteRelations(relationData);
625: insertRelations(relationData);
626:
627: relationData.addedRelations.clear();
628: relationData.removedRelations.clear();
629: relationData.notRelatedPairs.clear();
630: }
631: }
632: }
633:
634: public void passivateEntity(EntityEnterpriseContext ctx) {
635: passivateEntityCommand.execute(ctx);
636: }
637:
638: public void removeEntity(EntityEnterpriseContext ctx)
639: throws RemoveException, RemoteException {
640: removeEntityCommand.execute(ctx);
641: }
642:
643: //
644: // Relationship Commands
645: //
646: public Collection loadRelation(JDBCCMRFieldBridge cmrField,
647: Object pk) {
648: return loadRelationCommand.execute(cmrField, pk);
649: }
650:
651: private void deleteRelations(RelationData relationData) {
652: deleteRelationsCommand.execute(relationData);
653: }
654:
655: private void insertRelations(RelationData relationData) {
656: insertRelationsCommand.execute(relationData);
657: }
658:
659: private JDBCEntityMetaData loadJDBCEntityMetaData()
660: throws DeploymentException {
661: ApplicationMetaData amd = container.getBeanMetaData()
662: .getApplicationMetaData();
663:
664: // Get JDBC MetaData
665: JDBCApplicationMetaData jamd = (JDBCApplicationMetaData) amd
666: .getPluginData(CMP_JDBC);
667:
668: if (jamd == null) {
669: // we are the first cmp entity to need jbosscmp-jdbc.
670: // Load jbosscmp-jdbc.xml for the whole application
671: JDBCXmlFileLoader jfl = new JDBCXmlFileLoader(amd,
672: container.getClassLoader(), container
673: .getLocalClassLoader(), log);
674:
675: jamd = jfl.load();
676: amd.addPluginData(CMP_JDBC, jamd);
677: }
678:
679: // Get JDBC Bean MetaData
680: String ejbName = container.getBeanMetaData().getEjbName();
681: JDBCEntityMetaData metadata = jamd.getBeanByEjbName(ejbName);
682: if (metadata == null) {
683: throw new DeploymentException("No metadata found for bean "
684: + ejbName);
685: }
686: return metadata;
687: }
688:
689: // Inner
690:
691: private final class CascadeDeleteRegistry {
692: private Set scheduled;
693:
694: public void scheduleAll(List pks) {
695: if (scheduled == null) {
696: scheduled = new HashSet();
697: }
698: scheduled.addAll(pks);
699: }
700:
701: public boolean unschedule(Object pk) {
702: return scheduled.remove(pk);
703: }
704: }
705: }
|