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: *
023: * Free Software Foundation, Inc.
024: * 59 Temple Place, Suite 330
025: * Boston, MA 02111-1307 USA
026: *
027: * @author Scott Ferguson
028: */
029:
030: package com.caucho.ejb.entity;
031:
032: import com.caucho.amber.AmberException;
033: import com.caucho.amber.entity.AmberEntityHome;
034: import com.caucho.amber.entity.EntityItem;
035: import com.caucho.amber.manager.AmberConnection;
036: import com.caucho.ejb.AbstractContext;
037: import com.caucho.ejb.AbstractServer;
038: import com.caucho.ejb.EJBExceptionWrapper;
039: import com.caucho.ejb.FinderExceptionWrapper;
040: import com.caucho.ejb.manager.EjbContainer;
041: import com.caucho.ejb.protocol.AbstractHandle;
042: import com.caucho.util.Alarm;
043: import com.caucho.util.CharBuffer;
044: import com.caucho.util.L10N;
045:
046: import javax.ejb.EJBHome;
047: import javax.ejb.EJBLocalHome;
048: import javax.ejb.EJBException;
049: import javax.ejb.FinderException;
050: import javax.ejb.HomeHandle;
051: import javax.sql.DataSource;
052: import java.lang.reflect.Constructor;
053: import java.lang.reflect.Field;
054: import java.rmi.RemoteException;
055: import java.sql.*;
056: import java.util.ArrayList;
057: import java.util.Collections;
058: import java.util.Comparator;
059: import java.util.logging.*;
060:
061: /**
062: * EntityServer is a container for the instances of an entity bean.
063: */
064: public class EntityServer extends AbstractServer {
065: private static final Logger log = Logger
066: .getLogger(EntityServer.class.getName());
067:
068: private static final L10N L = new L10N(EntityServer.class);
069:
070: private EntityCache _entityCache;
071:
072: private DataSource _dataSource;
073:
074: private HomeHandle _homeHandle;
075: private QEntityContext _homeContext;
076:
077: private int _random;
078:
079: private boolean _isInit;
080: private boolean _isCMP;
081:
082: private boolean _isLoadLazyOnTransaction = true;
083:
084: private Field[] _primaryKeyFields;
085:
086: private Throwable _exception;
087:
088: private AmberEntityHome _amberEntityHome;
089:
090: private ArrayList<RemoveListener> _removeListeners = new ArrayList<RemoveListener>();
091: private ArrayList<EntityServer> _updateListeners;
092:
093: private Constructor _contextConstructor;
094:
095: private boolean _isCacheable = true;
096: // private long _cacheTimeout = 5000;
097: private long _cacheTimeout = 1000;
098: private long _invalidateTime;
099:
100: private int _jdbcIsolation = -1;
101:
102: /**
103: * Creates a new EntityServer.
104: *
105: * @param serverId the entity server's unique identifier
106: * @param allowJVMCall allows fast calls to the same JVM (with serialization)
107: * @param config the EJB configuration.
108: */
109: public EntityServer(EjbContainer ejbContainer) {
110: super (ejbContainer);
111:
112: _entityCache = _ejbContainer.getEntityCache();
113: _entityCache.start();
114:
115: // getPersistentManager().setHome(config.getName(), this);
116: // _dataSource = config.getDataSource();
117:
118: //if (_dataSource == null)
119: // _dataSource = ejbManager.getDataSource();
120: }
121:
122: /**
123: * Gets the primary key class
124: */
125: public Class getPrimaryKeyClass() {
126: return _primaryKeyClass;
127: }
128:
129: //
130: // configuration
131: //
132:
133: /**
134: * Sets CMP.
135: */
136: public void setCMP(boolean isCMP) {
137: _isCMP = isCMP;
138: }
139:
140: /**
141: * Sets CMP.
142: */
143: public boolean isCMP() {
144: return _isCMP;
145: }
146:
147: /**
148: * Gets the persistence scheme for the entity bean
149: */
150: public boolean getBeanManagedPersistence() {
151: return !_isCMP;
152: }
153:
154: /**
155: * Sets true if entities should be loaded lazily on transaction.
156: */
157: public boolean isLoadLazyOnTransaction() {
158: return _isLoadLazyOnTransaction;
159: }
160:
161: /**
162: * Sets true if entities should be loaded lazily on transaction.
163: */
164: public void setLoadLazyOnTransaction(boolean isLoadLazy) {
165: _isLoadLazyOnTransaction = isLoadLazy;
166: }
167:
168: public void setJdbcIsolation(int isolation) {
169: _jdbcIsolation = isolation;
170: }
171:
172: /**
173: * Returns true if the bean should be loaded.
174: *
175: * @param loadTime the time the bean was last loaded.
176: */
177: public boolean doLoad(long loadTime) {
178: if (loadTime <= _invalidateTime)
179: return true;
180:
181: long expiresTime = Alarm.getCurrentTime() - _cacheTimeout;
182:
183: return loadTime <= expiresTime;
184: }
185:
186: /**
187: * Sets the amber entity home.
188: */
189: public void setAmberEntityHome(AmberEntityHome home) {
190: _amberEntityHome = home;
191: }
192:
193: /**
194: * Sets the amber entity home.
195: */
196: public AmberEntityHome getAmberEntityHome() {
197: return _amberEntityHome;
198: }
199:
200: /**
201: * Loads an amber entity.
202: */
203: /*
204: public EntityItem loadEntityItem(Object key)
205: throws AmberException
206: {
207: return _amberEntityHome.loadEntityItem(key);
208: }
209: */
210:
211: /**
212: * Invalidates all cache entries, forcing them to reload.
213: */
214: public void invalidateCache() {
215: _invalidateTime = Alarm.getCurrentTime();
216: }
217:
218: /**
219: * Sets the primary key class.
220: */
221: public void setPrimaryKeyClass(Class cl) {
222: _primaryKeyClass = cl;
223: }
224:
225: /**
226: * Initializes the EntityServer, generating and compiling the skeletons
227: * if necessary.
228: */
229: public void init() throws Exception {
230: super .init();
231:
232: try {
233: // _isCacheable = _config.isCacheable();
234: //_cacheTimeout = _config.getCacheTimeout();
235:
236: /*
237: if (cacheSize < 1024)
238: cacheSize = 1024;
239: */
240:
241: // _jdbcIsolation = _ejbManager.getJDBCIsolation();
242: Class[] param = new Class[] { EntityServer.class };
243: _contextConstructor = _contextImplClass
244: .getConstructor(param);
245:
246: _homeContext = (QEntityContext) _contextConstructor
247: .newInstance(this );
248:
249: _localHome = _homeContext.createLocalHome();
250: _remoteHomeView = _homeContext.createRemoteHomeView();
251:
252: /*
253: if (_homeStubClass != null) {
254: _remoteHomeView = _homeContext.createRemoteHomeView();
255:
256: if (_config.getJndiName() != null) {
257: Context ic = new InitialContext();
258: ic.rebind(_config.getJndiName(), this);
259: }
260: }
261: */
262:
263: initRelations();
264:
265: if (_isCMP) {
266: // _amberEntityHome = getServerManager().getAmberEntityHome(name);
267: //_amberEntityHome = getContainer().getAmberEntityHome(_config.getName());
268: _amberEntityHome
269: .setEntityFactory(new AmberEntityFactory(this ));
270: }
271:
272: Class primaryKeyClass = getPrimaryKeyClass();
273:
274: if (primaryKeyClass != null
275: && !primaryKeyClass.isPrimitive()
276: && !primaryKeyClass.getName().startsWith(
277: "java.lang.")) {
278: _primaryKeyFields = primaryKeyClass.getFields();
279: }
280:
281: log.config(this + " initialized");
282: } catch (Exception e) {
283: _exception = e;
284:
285: throw e;
286: }
287: }
288:
289: /**
290: * Returns the 3.0 remote stub for the container
291: */
292: @Override
293: public Object getRemoteObject(Class api, String protocol) {
294: if (api == getRemoteHomeClass())
295: return _remoteHomeView;
296: else
297: return null;
298: }
299:
300: /**
301: * Returns the 3.0 remote stub for the container
302: */
303: @Override
304: public Object getLocalObject(Class api) {
305: if (api == getRemoteHomeClass())
306: return _remoteHomeView;
307: else
308: return null;
309: }
310:
311: /**
312: * Returns the 3.0 remote stub for the container
313: */
314: @Override
315: public Object getLocalProxy(Class api) {
316: if (api == getRemoteHomeClass())
317: return _remoteHomeView;
318: else
319: return null;
320: }
321:
322: /**
323: * Returns the 3.0 remote stub for the container
324: */
325: @Override
326: public Object getRemoteObject(Object key) throws FinderException {
327: AbstractContext cxt = getContext(key);
328:
329: if (cxt != null)
330: return cxt.getRemoteView();
331: else
332: return null;
333: }
334:
335: /**
336: * Initialize the callbacks required to manage relations. Primarily
337: * the callbacks are need to make sure collections are updated when
338: * the target changes or is deleted.
339: */
340: private void initRelations() {
341: if (!isCMP())
342: return;
343:
344: /*
345: PersistentBean bean = _config.getPersistentBean();
346: ArrayList<PersistentBean> oldBeans = new ArrayList<PersistentBean>();
347:
348: Iterator iter = bean.getRelations();
349: while (iter.hasNext()) {
350: PersistentRelation rel = (PersistentRelation) iter.next();
351: PersistentBean targetBean = rel.getTargetBean();
352:
353: addTargetBean(oldBeans, targetBean);
354: }
355: */
356: }
357:
358: /*
359: private void addTargetBean(ArrayList<PersistentBean> oldBeans,
360: PersistentBean targetBean)
361: {
362: if (! oldBeans.contains(targetBean)) {
363: oldBeans.add(targetBean);
364:
365: String ejbName = targetBean.getName();
366:
367: EntityServer targetServer = (EntityServer) _ejbManager.getServer(ejbName);
368:
369: if (targetServer == null) {
370: PersistentBean bean = _config.getPersistentBean();
371: throw new RuntimeException("unknown ejb '" + ejbName + "' in '" +
372: bean.getName() + "'");
373: }
374:
375: targetServer.addRemoveListener(_homeContext);
376: }
377: }
378: */
379:
380: /**
381: * Creates a handle for the primary key.
382: */
383: protected AbstractHandle createHandle(Object primaryKey) {
384: return getHandleEncoder().createHandle(encodeId(primaryKey));
385: }
386:
387: /**
388: * Returns the encoded id.
389: */
390: public String encodeId(Object primaryKey) {
391: if (_primaryKeyFields == null)
392: return String.valueOf(primaryKey);
393:
394: try {
395: CharBuffer cb = new CharBuffer();
396:
397: for (int i = 0; i < _primaryKeyFields.length; i++) {
398: if (i != 0)
399: cb.append(',');
400:
401: cb.append(_primaryKeyFields[i].get(primaryKey));
402: }
403:
404: return cb.toString();
405: } catch (IllegalAccessException e) {
406: throw new EJBExceptionWrapper(e);
407: }
408: }
409:
410: /**
411: * Associate the skeleton with a key.
412: */
413: public void postCreate(Object key, QEntityContext cxt) {
414: _entityCache.putEntityIfNew(this , key, cxt);
415: }
416:
417: /**
418: * Adds a remove listener.
419: *
420: * @param listener the home context that's listening for events.
421: * @param listenClass the class for this server.
422: */
423: public void addRemoveListener(QEntityContext context) {
424: //removeListeners.add(new RemoveListener(listener, listenClass));
425: RemoveListener listener = null;
426: //listener = new RemoveListener(context, _config.getLocalObjectClass());
427:
428: if (!_removeListeners.contains(listener))
429: _removeListeners.add(listener);
430: }
431:
432: /**
433: * Adds an update listener.
434: */
435: public void addUpdateListener(EntityServer listener) {
436: if (_updateListeners == null)
437: _updateListeners = new ArrayList<EntityServer>();
438:
439: if (!_updateListeners.contains(listener))
440: _updateListeners.add(listener);
441: }
442:
443: /**
444: * Remove an object.
445: */
446: public void remove(Object key) {
447: for (int i = _removeListeners.size() - 1; i >= 0; i--) {
448: RemoveListener listener = _removeListeners.get(i);
449:
450: try {
451: listener._listener._caucho_remove_callback(
452: listener._listenClass, key);
453: } catch (Throwable e) {
454: EJBExceptionWrapper.createRuntime(e);
455: }
456: }
457: }
458:
459: public void removeCache(Object key) {
460: _entityCache.removeEntity(this , key);
461: }
462:
463: /**
464: * Returns the underlying object by searching the primary key.
465: */
466: public AbstractContext findByPrimaryKey(Object key)
467: throws FinderException {
468: return getContext(key, _isCMP);
469: }
470:
471: /**
472: * Returns the underlying object by searching the primary key.
473: */
474: public AbstractContext findByPrimaryKey(int key)
475: throws FinderException {
476: return getContext(new Long(key), _isCMP);
477: }
478:
479: /**
480: * Returns the underlying object by searching the primary key.
481: *
482: * In this case, the object is known to exist.
483: */
484: public AbstractContext findKnownObjectByPrimaryKey(Object key) {
485: // XXX:
486: if (key == null)
487: return null;
488:
489: try {
490: return getContext(key, false);
491: } catch (FinderException e) {
492: throw EJBExceptionWrapper.createRuntime(e);
493: }
494: }
495:
496: /**
497: * Returns the underlying object by searching the primary key.
498: *
499: * In this case, the object is known to exist.
500: */
501: public Object findKnownObjectByPrimaryKey(int key) {
502: return findKnownObjectByPrimaryKey(new Integer(key));
503: }
504:
505: /**
506: * Returns the EJBHome stub for the container
507: */
508: public EJBHome getClientHome() throws RemoteException {
509: if (!_isInit) {
510: /*
511: try {
512: _config.validateJDBC();
513: } catch (ConfigException e) {
514: throw new EJBExceptionWrapper(e);
515: }
516: */
517:
518: _isInit = true;
519: }
520:
521: /*
522: if (_remoteHome == null) {
523: try {
524: _remoteHome = _jvmClient.createHomeStub();
525: } catch (Exception e) {
526: EJBExceptionWrapper.createRuntime(e);
527: }
528: }
529: */
530:
531: return _remoteHome;
532: }
533:
534: /**
535: * Returns the EJBHome stub for the container
536: */
537: public EJBHome getEJBHome() {
538: return _remoteHomeView;
539: /*
540: if (_remoteHome == null) {
541: if (! _isInit) {
542: try {
543: _config.validateJDBC();
544: } catch (ConfigException e) {
545: throw new EJBExceptionWrapper(e);
546: }
547:
548: _isInit = true;
549: }
550:
551: try {
552: _remoteHome = _jvmClient.createHomeStub();
553: } catch (Exception e) {
554: EJBExceptionWrapper.createRuntime(e);
555: }
556: }
557:
558: return _remoteHome;
559: */
560: }
561:
562: /**
563: * Returns the EJBLocalHome stub for the container
564: */
565: public EJBLocalHome getClientLocalHome() {
566: if (!_isInit) {
567: /*
568: try {
569: _config.validateJDBC();
570: } catch (ConfigException e) {
571: throw new EJBExceptionWrapper(e);
572: }
573: */
574:
575: _isInit = true;
576: }
577:
578: return _localHome;
579: }
580:
581: public Object getHomeObject() {
582: if (!_isInit) {
583: /*
584: try {
585: _config.validateJDBC();
586: } catch (ConfigException e) {
587: throw new EJBExceptionWrapper(e);
588: }
589: */
590:
591: _isInit = true;
592: }
593:
594: return _remoteHomeView;
595: }
596:
597: public AbstractContext getContext(Object key)
598: throws FinderException {
599: return getContext(key, _isCMP);
600: }
601:
602: /**
603: * Finds the remote bean by its key.
604: *
605: * @param key the remote key
606: *
607: * @return the remote interface of the entity.
608: */
609: public AbstractContext getContext(Object key, boolean forceLoad)
610: throws FinderException {
611: return getContext(key, forceLoad, true);
612: }
613:
614: /**
615: * Finds the remote bean by its key.
616: *
617: * @param key the remote key
618: *
619: * @return the remote interface of the entity.
620: */
621: public AbstractContext getContext(Object key, boolean forceLoad,
622: boolean isFindEntityItem) throws FinderException {
623: if (key == null)
624: return null;
625:
626: QEntityContext cxt = _entityCache.getEntity(this , key);
627:
628: try {
629: if (cxt == null) {
630: cxt = (QEntityContext) _contextConstructor
631: .newInstance(this );
632: cxt.setPrimaryKey(key);
633:
634: cxt = _entityCache.putEntityIfNew(this , key, cxt);
635:
636: EntityItem amberItem = null;
637:
638: /*
639: // ejb/061c
640: if (isFindEntityItem && _isCMP) {
641: AmberConnection aConn;
642: aConn = _amberEntityHome.getManager().getCacheConnection();
643:
644: try {
645: // ejb/06d3
646: amberItem = _amberEntityHome.findEntityItem(aConn,
647: key,
648: forceLoad);
649: } catch (AmberException e) {
650: String name = getEJBName();
651:
652: FinderException exn = new ObjectNotFoundException(L.l("'{0}' is an unknown entity.",
653: name + "[" + key + "]"));
654: exn.initCause(e);
655: throw exn;
656: } finally {
657: aConn.freeConnection();
658: }
659:
660: if (amberItem == null) {
661: throw new ObjectNotFoundException(L.l("'{0}' is an unknown entity.",
662: getEJBName() + "[" + key + "]"));
663: }
664: }
665:
666: if (amberItem != null)
667: cxt.__caucho_setAmber(amberItem);
668: */
669: }
670:
671: // ejb/0d33 vs ejb/0d00 vs ejb/0500
672: if (forceLoad
673: && (!_isLoadLazyOnTransaction || getTransactionManager()
674: .getTransaction() == null)) {
675: try {
676: cxt._caucho_load();
677: } catch (Exception e) {
678: throw e;
679: }
680: }
681:
682: /*
683: try {
684: cxt._caucho_load();
685: } catch (Exception e) {
686: throw e;
687: }
688: */
689:
690: // XXX: ejb/061c, moved to the top.
691: // cxt = _ejbManager.putEntityIfNew(this, key, cxt);
692: return cxt;
693: } catch (FinderException e) {
694: throw e;
695: } catch (Exception e) {
696: throw FinderExceptionWrapper.create(e);
697: }
698: }
699:
700: public EntityItem getAmberCacheItem(Object key) {
701: if (key == null)
702: throw new NullPointerException();
703:
704: AmberConnection aConn;
705: aConn = _amberEntityHome.getManager().getCacheConnection();
706:
707: try {
708: // ejb/06d3
709: EntityItem amberItem = aConn.loadCacheItem(_amberEntityHome
710: .getJavaClass(), key, _amberEntityHome);
711:
712: return amberItem;
713: } catch (AmberException e) {
714: throw new EJBException(e);
715: } finally {
716: aConn.freeConnection();
717: }
718: }
719:
720: /**
721: * Returns a new connection.
722: */
723: public Connection getConnection(boolean isReadOnly)
724: throws java.sql.SQLException {
725: if (isReadOnly)
726: return _dataSource.getConnection();
727: else {
728: Connection conn = _dataSource.getConnection();
729:
730: if (_jdbcIsolation > 0)
731: conn.setTransactionIsolation(_jdbcIsolation);
732:
733: return conn;
734: }
735: }
736:
737: /**
738: * Updates the named entity bean
739: */
740: public void update(Object key) {
741: if (key == null)
742: return;
743:
744: QEntityContext cxt = _entityCache.getEntity(this , key);
745:
746: if (cxt == null)
747: return;
748:
749: // XXX: only update in the transaction?
750: // why doesn't the transaction have the update?
751: cxt.update();
752: }
753:
754: /**
755: * Returns the server's DataSource
756: */
757: public DataSource getDataSource() {
758: return _dataSource;
759: }
760:
761: /**
762: * Sets the server's DataSource
763: */
764: public void setDataSource(DataSource dataSource) {
765: _dataSource = dataSource;
766: }
767:
768: public Connection getConnection() throws SQLException {
769: return getDataSource().getConnection();
770: }
771:
772: /**
773: * Updates the named entity bean
774: */
775: public void updateView(Object key) {
776: if (_updateListeners == null)
777: return;
778:
779: for (int i = _updateListeners.size() - 1; i >= 0; i--) {
780: EntityServer server = _updateListeners.get(i);
781:
782: server.update(key);
783: }
784: }
785:
786: /**
787: * Returns a random string for a new id
788: *
789: * @param max the previous maximum value
790: * @param i the repetition index
791: */
792: public String randomString(int max, int i) {
793: return String.valueOf(++_random);
794: }
795:
796: /**
797: * Returns a random integer for a new id
798: *
799: * @param max the previous maximum value
800: * @param i the repetition index
801: */
802: public int randomInt(int max, int i) {
803: return max;
804: }
805:
806: public static IllegalStateException createGetStateException(
807: int state) {
808: switch (state) {
809: case QEntity._CAUCHO_IS_REMOVED:
810: return new IllegalStateException(L
811: .l("Can't access CMP field for a removed object."));
812:
813: case QEntity._CAUCHO_IS_DEAD:
814: return new IllegalStateException(
815: L
816: .l("Can't access CMP field for a dead object. The object is dead due to a runtime exception and rollback."));
817:
818: case QEntity._CAUCHO_IS_NEW:
819: return new IllegalStateException(
820: L
821: .l("Can't access CMP field for an uninitialized object."));
822:
823: case QEntity._CAUCHO_IS_HOME:
824: return new IllegalStateException(L
825: .l("Can't access CMP field from a Home object."));
826:
827: default:
828: return new IllegalStateException(L
829: .l("Can't access CMP field from an unknown state."));
830: }
831: }
832:
833: public static IllegalStateException createSetStateException(
834: int state) {
835: switch (state) {
836: case QEntity._CAUCHO_IS_REMOVED:
837: return new IllegalStateException(L
838: .l("Can't set CMP field for a removed object."));
839:
840: case QEntity._CAUCHO_IS_DEAD:
841: return new IllegalStateException(
842: L
843: .l("Can't set CMP field for a dead object. The object is dead due to a runtime exception and rollback."));
844:
845: case QEntity._CAUCHO_IS_NEW:
846: return new IllegalStateException(
847: L
848: .l("Can't set CMP field for an uninitialized object."));
849:
850: case QEntity._CAUCHO_IS_HOME:
851: return new IllegalStateException(L
852: .l("Can't set CMP field from a Home object."));
853:
854: default:
855: return new IllegalStateException(L
856: .l("Can't set CMP field from an unknown state."));
857: }
858: }
859:
860: /**
861: * Cleans up the entity server nicely.
862: */
863: public void destroy() {
864: ArrayList<QEntityContext> beans = new ArrayList<QEntityContext>();
865:
866: _entityCache.removeBeans(beans, this );
867:
868: // only purpose of the sort is to make the qa order consistent
869: Collections.sort(beans, new EntityCmp());
870:
871: for (int i = 0; i < beans.size(); i++) {
872: QEntityContext cxt = beans.get(i);
873:
874: try {
875: cxt.destroy();
876: } catch (Exception e) {
877: log.log(Level.WARNING, e.toString(), e);
878: }
879: }
880:
881: QEntityContext homeContext = _homeContext;
882: _homeContext = null;
883:
884: try {
885: if (homeContext != null)
886: homeContext.destroy();
887: } catch (Exception e) {
888: log.log(Level.WARNING, e.toString(), e);
889: }
890:
891: super .destroy();
892: }
893:
894: static class RemoveListener {
895: QEntityContext _listener;
896: Class _listenClass;
897:
898: RemoveListener(QEntityContext listener, Class listenClass) {
899: _listener = listener;
900: _listenClass = listenClass;
901: }
902:
903: public boolean equals(Object o) {
904: if (!(o instanceof RemoveListener))
905: return false;
906:
907: RemoveListener listener = (RemoveListener) o;
908:
909: return (_listener.equals(listener._listener) && _listenClass
910: .equals(listener._listenClass));
911: }
912:
913: public String toString() {
914: return "RemoveListener[" + _listener + "]";
915: }
916: }
917:
918: static class EntityCmp implements Comparator<QEntityContext> {
919: public int compare(QEntityContext ca, QEntityContext cb) {
920: try {
921: String sa = String.valueOf(ca.getPrimaryKey());
922: String sb = String.valueOf(cb.getPrimaryKey());
923:
924: return sa.compareTo(sb);
925: } catch (Exception e) {
926: return 0;
927: }
928: }
929: }
930: }
|