001: /*
002: * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
003: *
004: * This file is part of Resin(R) Open Source
005: *
006: * Each copy or derived work must preserve the copyright notice and this
007: * notice unmodified.
008: *
009: * Resin Open Source is free software; you can redistribute it and/or modify
010: * it under the terms of the GNU General Public License as published by
011: * the Free Software Foundation; either version 2 of the License, or
012: * (at your option) any later version.
013: *
014: * Resin Open Source is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
017: * of NON-INFRINGEMENT. See the GNU General Public License for more
018: * details.
019: *
020: * You should have received a copy of the GNU General Public License
021: * along with Resin Open Source; if not, write to the
022: * Free Software Foundation, Inc.
023: * 59 Temple Place, Suite 330
024: * Boston, MA 02111-1307 USA
025: *
026: * @author Scott Ferguson
027: */
028:
029: package com.caucho.amber.entity;
030:
031: import com.caucho.amber.AmberException;
032: import com.caucho.amber.AmberRuntimeException;
033: import com.caucho.amber.AmberObjectNotFoundException;
034: import com.caucho.amber.manager.AmberConnection;
035: import com.caucho.amber.manager.AmberPersistenceUnit;
036: import com.caucho.amber.query.CacheUpdate;
037: import com.caucho.amber.type.*;
038: import com.caucho.config.ConfigException;
039: import com.caucho.log.Log;
040: import com.caucho.util.L10N;
041:
042: import java.lang.ref.SoftReference;
043: import java.lang.reflect.Method;
044: import java.lang.reflect.Modifier;
045: import java.sql.ResultSet;
046: import java.sql.SQLException;
047: import java.util.ArrayList;
048: import java.util.Map;
049: import java.util.logging.Level;
050: import java.util.logging.Logger;
051:
052: /**
053: * Manages the set of persistent beans.
054: */
055: public class AmberEntityHome {
056: private static final L10N L = new L10N(AmberEntityHome.class);
057: private static final Logger log = Log.open(AmberEntityHome.class);
058:
059: private AmberPersistenceUnit _manager;
060: private EntityType _entityType;
061:
062: private EntityFactory _entityFactory = new EntityFactory();
063:
064: private Entity _homeBean;
065:
066: private ArrayList<SoftReference<CacheUpdate>> _cacheUpdates = new ArrayList<SoftReference<CacheUpdate>>();
067:
068: private EntityKey _cacheKey = new EntityKey();
069:
070: private volatile boolean _isInit;
071:
072: private RuntimeException _configException;
073:
074: private Method _cauchoGetBeanMethod;
075:
076: public AmberEntityHome(AmberPersistenceUnit manager, EntityType type) {
077: _manager = manager;
078: _entityType = type;
079:
080: try {
081: Class cl = Class
082: .forName("com.caucho.ejb.entity.EntityObject");
083: _cauchoGetBeanMethod = cl.getMethod("_caucho_getBean",
084: new Class[0]);
085: } catch (Exception e) {
086: log.log(Level.FINER, e.toString(), e);
087: }
088: }
089:
090: /**
091: * Returns the getBean method to instantiate the Amber object.
092: */
093: public Method getCauchoGetBeanMethod() {
094: return _cauchoGetBeanMethod;
095: }
096:
097: /**
098: * Returns the manager.
099: */
100: public AmberPersistenceUnit getManager() {
101: return _manager;
102: }
103:
104: /**
105: * Returns the entity type
106: */
107: public EntityType getEntityType() {
108: return _entityType;
109: }
110:
111: /**
112: * Returns the entity type
113: */
114: public EntityType getRootType() {
115: return (EntityType) _entityType.getRootType();
116: }
117:
118: /**
119: * Returns the java class.
120: */
121: public Class getJavaClass() {
122: return _entityType.getInstanceClass();
123: }
124:
125: /**
126: * Returns the entity factory.
127: */
128: public EntityFactory getEntityFactory() {
129: return _entityFactory;
130: }
131:
132: /**
133: * Sets the entity factory.
134: */
135: public void setEntityFactory(EntityFactory factory) {
136: _entityFactory = factory;
137: }
138:
139: /**
140: * Returns the cache timeout.
141: */
142: public long getCacheTimeout() {
143: return _entityType.getCacheTimeout();
144: }
145:
146: /**
147: * Returns the instance class.
148: */
149: public Class getInstanceClass() {
150: return _entityType.getInstanceClass();
151: }
152:
153: /**
154: * Link the classes.
155: */
156: void link() throws ConfigException {
157: // _entityClass.link(_manager);
158: }
159:
160: /**
161: * Initialize the home.
162: */
163: public void init() throws ConfigException {
164: synchronized (this ) {
165: if (_isInit)
166: return;
167: _isInit = true;
168: }
169:
170: _entityType.init();
171:
172: try {
173: Class instanceClass = _entityType.getInstanceClass();
174:
175: if (!Modifier.isAbstract(instanceClass.getModifiers()))
176: _homeBean = (Entity) instanceClass.newInstance();
177: } catch (Exception e) {
178: _entityType.setConfigException(e);
179:
180: _configException = ConfigException.create(e);
181: throw _configException;
182: }
183:
184: _entityType.start();
185: }
186:
187: /**
188: * Returns the entity from the key.
189: */
190: public Object getKeyFromEntity(Entity entity) throws AmberException {
191: // return _entityType.getId().getType().getValue(obj);
192: return null;
193: }
194:
195: /**
196: * Converts a long key to the key.
197: */
198: public Object toObjectKey(long key) {
199: return _entityType.getId().toObjectKey(key);
200: }
201:
202: /*
203: private Entity load(AmberConnection aConn,
204: Object key)
205: throws AmberException
206: {
207: EntityItem existingItem = _manager.getEntity(getRootType(), key);
208:
209: return find(aConn, key, existingItem, true, 0, 0);
210: }
211: */
212:
213: /**
214: * Finds by the primary key.
215: */
216: /*
217: private Entity load(AmberConnection aConn,
218: Object key,
219: long notExpiringLoadMask,
220: int notExpiringGroup)
221: throws AmberException
222: {
223: EntityItem existingItem = _manager.getEntity(getRootType(), key);
224:
225: return find(aConn, key, existingItem,
226: true, notExpiringLoadMask, notExpiringGroup);
227: }
228: */
229:
230: /**
231: * Finds by the primary key.
232: */
233: /*
234: private Entity loadLazy(AmberConnection aConn, Object key)
235: throws AmberException
236: {
237: EntityItem existingItem = _manager.getEntity(getRootType(), key);
238:
239: return find(aConn, key, existingItem, false, 0, 0);
240: }
241: */
242:
243: /**
244: * Finds by the primary key.
245: */
246: public EntityItem findItem(AmberConnection aConn, ResultSet rs,
247: int index) throws SQLException {
248: EntityItem item = _homeBean.__caucho_home_find(aConn, this , rs,
249: index);
250:
251: return item;
252: }
253:
254: /**
255: * Finds by the primary key.
256: */
257: public Object loadFull(AmberConnection aConn, ResultSet rs,
258: int index) throws SQLException {
259: // jpa/0l43
260: Entity entity;
261:
262: if (_homeBean == null)
263: throw new NullPointerException("HOME:" + this );
264:
265: entity = aConn.getSubEntity(_homeBean.getClass(), rs
266: .getObject(index));
267:
268: if (entity != null) {
269: if (entity.__caucho_getEntityState().isManaged())
270: return entity;
271: }
272:
273: EntityItem item = findItem(aConn, rs, index);
274:
275: if (item == null)
276: return null;
277:
278: entity = null;
279:
280: Object value = aConn.getEntityLazy(item);
281:
282: if (aConn.isActiveTransaction()) {
283: if (value instanceof Entity)
284: entity = (Entity) value;
285: else if (_cauchoGetBeanMethod != null) {
286: try {
287: entity = (Entity) _cauchoGetBeanMethod.invoke(
288: value, new Object[0]);
289: entity.__caucho_makePersistent(aConn, item);
290: } catch (Exception e) {
291: log.log(Level.FINER, e.toString(), e);
292: }
293: }
294:
295: if (entity == null)
296: entity = aConn.getEntity(item);
297: } else
298: entity = item.getEntity();
299:
300: int keyLength = _entityType.getId().getKeyCount();
301:
302: entity.__caucho_load(aConn, rs, index + keyLength);
303:
304: return entity;
305: }
306:
307: /**
308: * Finds by the primary key.
309: */
310: public Object loadLazy(AmberConnection aConn, ResultSet rs,
311: int index) throws SQLException {
312: EntityItem item = findItem(aConn, rs, index);
313:
314: if (item == null)
315: return null;
316:
317: return aConn.getEntity(item);
318: }
319:
320: /*
321: private Entity find(AmberConnection aConn,
322: Object key,
323: boolean isLoad)
324: throws AmberException
325: {
326: EntityItem existingItem = _manager.getEntity(getRootType(), key);
327:
328: return find(aConn, key, existingItem, isLoad, 0, 0);
329: }
330: */
331:
332: /**
333: * Finds an entity based on the primary key.
334: *
335: * @param key the primary key
336: * @param aConn the Amber connection to associate with the loaded item
337: * @param isLoad if true, try to load the bean
338: */
339: /*
340: public Entity find(AmberConnection aConn,
341: Object key,
342: EntityItem cacheItem,
343: boolean isLoad,
344: long notExpiringLoadMask,
345: int notExpiringGroup)
346: throws AmberException
347: {
348: try {
349: // jpa/0o01, jpa/0o41
350:
351: // jpa/0l48: checks if cacheItem is new or existing (below).
352: // EntityItem existingItem = _manager.getEntity(getRootType(), key);
353:
354: if (log.isLoggable(Level.FINER)) {
355: log.log(Level.FINER, L.l("AmberEntityHome.find class: {0} PK: {1} isLoad: {2} xa: {3} notExpiringLoadMask: " + notExpiringLoadMask + " notExpiringGroup: " + notExpiringGroup,
356: _entityType.getClassName(), key,
357: isLoad, aConn.isActiveTransaction()));
358:
359: String msg;
360:
361: if (existingItem == null)
362: msg = "there is not a cache item for class: {0} PK: {1}. It will try to load it.";
363: else
364: msg = "found an existing cache item for class: {0} PK: {1}.";
365:
366: log.log(Level.FINER, L.l("AmberEntityHome.find: " + msg,
367: _entityType.getClassName(), key));
368: }
369:
370: Entity cacheEntity = cacheItem.getEntity();
371: AmberEntityHome home = cacheItem.getEntityHome();
372:
373: Entity entity = cacheEntity.__caucho_home_new(home, key);
374: entity.__caucho_makePersistent(aConn, cacheItem);
375:
376: // The entity is added for eagerly loading optimization.
377: aConn.addEntity(entity);
378:
379: try {
380: // jpa/0l48: inheritance loading optimization.
381: // jpa/0h20: no transaction, copy from the existing cache item.
382:
383: // Copy as much as possible from a new cache item or
384: // from the existing cache item if there is no transaction.
385: if (existingItem == null || ! aConn.isActiveTransaction()) {
386: if (log.isLoggable(Level.FINER)) {
387: String msg;
388:
389: if (existingItem == null)
390: msg = "a new cache item has been loaded and";
391: else
392: msg = "since there is no transaction, the existing cache item";
393:
394: log.log(Level.FINER, L.l( "AmberEntityHome.find: {0} will be copied to a context object class: {1} PK: {2}.",
395: msg, _entityType.getClassName(), key));
396: }
397:
398: // _manager.copyFromCacheItem(aConn, entity, cacheItem);
399: cacheItem.copyTo(entity, aConn);
400: }
401:
402: long loadMask = entity.__caucho_getLoadMask(notExpiringGroup);
403: loadMask |= notExpiringLoadMask;
404:
405: // jpa/0l42: loading optimization.
406: entity.__caucho_setLoadMask(loadMask, notExpiringGroup);
407:
408: if (isLoad) {
409: entity.__caucho_retrieve_eager(aConn);
410: }
411: else if (aConn.isActiveTransaction()) {
412: // jpa/0v33: within a transaction, cannot copy from cache.
413: entity.__caucho_retrieve_self(aConn);
414: }
415:
416: // XXX: should be handled above.
417: // else if (existingItem != null) {
418: // cacheItem.copyTo(entity, aConn);
419: // }
420: } catch (AmberObjectNotFoundException e) {
421: // 0g0q: if the entity is not found, removes it from context.
422: aConn.removeEntity(entity);
423:
424: if (_manager.isJPA())
425: return null;
426:
427: throw e;
428: }
429:
430: return entity;
431: } catch (Exception e) {
432: // jpa/0u0j
433: if (_manager.isJPA() && (e instanceof RuntimeException))
434: throw (RuntimeException) e;
435:
436: throw AmberException.create(e);
437: }
438: }
439: */
440:
441: /**
442: * Loads an entity based on the primary key.
443: *
444: * @param aConn the Amber connection to associate with the loaded item
445: * @param key the primary key
446: * @param isLoad if true, try to load the bean
447: */
448: /*
449: public EntityItem findEntityItem(AmberConnection aConn,
450: Object key,
451: boolean isLoad,
452: EntityItem item)
453: throws AmberException
454: {
455: if (log.isLoggable(Level.FINER))
456: log.log(Level.FINER, "findEntityItem: "+key+" "+isLoad);
457:
458: if (key == null)
459: return null; // ejb/0a06 throw new NullPointerException("primaryKey");
460:
461: try {
462: // XXX: ejb/0d01 should not check this.
463: // jpa/0y14 if (aConn.shouldRetrieveFromCache())
464: if (item == null)
465: item = _manager.getEntity(getRootType(), key);
466:
467: if (log.isLoggable(Level.FINER))
468: log.log(Level.FINER, "findEntityItem item is null? "+(item == null));
469:
470: if (item == null) {
471: if (_homeBean == null && _configException != null)
472: throw _configException;
473:
474: // jpa/0l00, jpa/0l32
475: // XXX: this is an initial optimization and bug fix also for jpa/0s29
476: // XXX: another point is inheritance with many-to-one (jpa/0l40 and jpa/0s29)
477: // still get twice the number of loading SQLs.
478: boolean loadFromResultSet = false; // ! getEntityType().hasDependent();
479:
480: Entity cacheEntity;
481:
482: cacheEntity = (Entity) _homeBean.__caucho_home_find(aConn, this, key);
483:
484: if (log.isLoggable(Level.FINER))
485: log.log(Level.FINER, "findEntityItem cacheEntity is null? "+(cacheEntity == null));
486:
487: // Object does not exist.
488: if (cacheEntity == null) {
489: if (_manager.isJPA())
490: return null;
491:
492: // ejb/0604
493: throw new AmberObjectNotFoundException("amber find: no matching object " + _entityType.getBeanClass().getName() + "[" + key + "]");
494: }
495:
496: item = new CacheableEntityItem(this, cacheEntity);
497:
498: // The cache entity is added after commit.
499: if (! aConn.isActiveTransaction()) {
500: if (isLoad) {
501: if (_manager.isJPA()) {
502: // jpa/0o03
503: item.loadEntity(aConn, 0);
504: }
505: else
506: item.loadEntity(0);
507: }
508:
509: // jpa/0g0p: only adds the cache entity if it is not within a transaction.
510: item = _manager.putEntity(getRootType(), key, item);
511:
512: if (log.isLoggable(Level.FINER))
513: log.log(Level.FINER, "findEntityItem after putEntity item is null? "+(item == null));
514: }
515: }
516: else if (isLoad) {
517: if (! aConn.isActiveTransaction()) {
518: if (_manager.isJPA()) {
519: // jpa/0v33
520: item.loadEntity(aConn, 0);
521: }
522: else
523: item.loadEntity(0);
524: }
525: }
526:
527: if (log.isLoggable(Level.FINER))
528: log.log(Level.FINER, "returning item is null? "+(item == null));
529:
530: return item;
531: } catch (Exception e) {
532: // jpa/0u0j
533: if (_manager.isJPA() && (e instanceof ClassCastException))
534: throw new IllegalArgumentException(e);
535:
536: throw AmberException.create(e);
537: }
538: }
539: */
540:
541: public EntityItem findEntityItem(AmberConnection aConn, Object key)
542: throws AmberException {
543: if (_homeBean == null && _configException != null)
544: throw _configException;
545:
546: Entity cacheEntity;
547:
548: cacheEntity = (Entity) _homeBean.__caucho_home_find(aConn,
549: this , key);
550:
551: if (cacheEntity != null)
552: return new CacheableEntityItem(this , cacheEntity);
553: else
554: return null;
555: }
556:
557: /**
558: * Loads an entity based on the primary key.
559: *
560: * @param key the primary key
561: * @param aConn the Amber connection to associate with the loaded item
562: * @param isLoad if true, try to load the bean
563: */
564: public EntityItem setEntityItem(Object key, EntityItem item)
565: throws AmberException {
566: if (key == null)
567: throw new NullPointerException("primaryKey");
568:
569: try {
570: item.getEntity().__caucho_setConnection(
571: _manager.getCacheConnection());
572:
573: return _manager.putEntity(getRootType(), key, item);
574: } catch (Exception e) {
575: throw AmberException.create(e);
576: }
577: }
578:
579: /**
580: * Loads an entity where the type is determined by a discriminator
581: *
582: * @param aConn the connection to associate with the entity
583: * @param key the primary key
584: * @param discriminator the object's discriminator
585: */
586: public EntityItem findDiscriminatorEntityItem(
587: AmberConnection aConn, Object key, String discriminator)
588: throws SQLException {
589: EntityItem item = null;
590:
591: // jpa/0l20
592: // XXX: ejb/0d01
593: // if (aConn.shouldRetrieveFromCache())
594: item = _manager.getEntity(getRootType(), key);
595:
596: if (item == null) {
597: EntityType subEntity = (EntityType) _entityType
598: .getSubClass(discriminator);
599:
600: Entity cacheEntity = subEntity.createBean();
601:
602: cacheEntity.__caucho_setPrimaryKey(key);
603: cacheEntity.__caucho_makePersistent(_manager
604: .getCacheConnection(), subEntity);
605:
606: item = new CacheableEntityItem(this , cacheEntity);
607:
608: // The cache entity is added after commit.
609: if (!aConn.isActiveTransaction()) {
610: item = _manager.putEntity(getRootType(), key, item);
611: }
612: }
613:
614: return item;
615: }
616:
617: /**
618: * Instantiates a new entity for this home.
619: */
620: public Entity newEntity(Object key) {
621: return (Entity) _homeBean.__caucho_home_new(this , key, null,
622: null);
623: }
624:
625: /**
626: * Loads an entity where the type is determined by a discriminator
627: *
628: * @param aConn the connection to associate with the entity
629: * @param key the primary key
630: * @param discriminator the object's discriminator
631: */
632: public Entity newDiscriminatorEntity(Object key,
633: String discriminator) {
634: if (discriminator == null || key == null)
635: throw new AmberRuntimeException(L.l(
636: "{0} is not a valid inheritance key.", key));
637:
638: EntityType subType = (EntityType) _entityType
639: .getSubClass(discriminator);
640:
641: return subType.getHome().newEntity(key);
642: }
643:
644: /**
645: * Finds by the primary key.
646: */
647: public Entity makePersistent(Entity entity, AmberConnection aConn,
648: boolean isLazy) throws SQLException {
649: entity.__caucho_makePersistent(aConn, _entityType);
650:
651: return entity;
652: }
653:
654: /**
655: * Saves based on the object.
656: */
657: public void save(AmberConnection aConn, Entity entity)
658: throws SQLException {
659: // Common to JPA and CMP.
660: entity.__caucho_lazy_create(aConn, _entityType);
661:
662: if (!_manager.isJPA())
663: entity.__caucho_create(aConn, _entityType);
664: }
665:
666: /**
667: * Deletes by the primary key.
668: */
669: public void delete(AmberConnection aConn, Object key)
670: throws SQLException {
671: _manager.removeEntity(getRootType(), key);
672:
673: /*
674: _entityType.childDelete(aConn, key);
675:
676: // XXX: possibly move somewhere else?
677: synchronized (_cacheUpdates) {
678: for (int i = _cacheUpdates.size() - 1; i >= 0; i--) {
679: SoftReference<CacheUpdate> ref = _cacheUpdates.get(i);
680: CacheUpdate update = ref.get();
681:
682: if (update == null)
683: _cacheUpdates.remove(i);
684: else
685: update.delete(primaryKey);
686: }
687: }
688: */
689: }
690:
691: /**
692: * Deletes by the primary key.
693: */
694: public void delete(AmberConnection aConn, long primaryKey)
695: throws SQLException {
696: /*
697: _entityClass.childDelete(session, primaryKey);
698:
699: // XXX: possibly move somewhere else?
700: synchronized (_cacheUpdates) {
701: for (int i = _cacheUpdates.size() - 1; i >= 0; i--) {
702: SoftReference<CacheUpdate> ref = _cacheUpdates.get(i);
703: CacheUpdate update = ref.get();
704:
705: if (update == null)
706: _cacheUpdates.remove(i);
707: else
708: update.delete(primaryKey);
709: }
710: }
711: */
712: }
713:
714: /**
715: * Update for a modification.
716: */
717: public void update(Entity entity) throws SQLException {
718: }
719:
720: /**
721: * Adds a cache update.
722: */
723: public void addUpdate(CacheUpdate update) {
724: _cacheUpdates.add(new SoftReference<CacheUpdate>(update));
725: }
726:
727: public String toString() {
728: return "AmberEntityHome[" + _entityType + "]";
729: }
730: }
|