KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > caucho > ejb > entity > EntityServer


1 /*
2  * Copyright (c) 1998-2006 Caucho Technology -- all rights reserved
3  *
4  * This file is part of Resin(R) Open Source
5  *
6  * Each copy or derived work must preserve the copyright notice and this
7  * notice unmodified.
8  *
9  * Resin Open Source is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * Resin Open Source is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
17  * of NON-INFRINGEMENT. See the GNU General Public License for more
18  * details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with Resin Open Source; if not, write to the
22  *
23  * Free Software Foundation, Inc.
24  * 59 Temple Place, Suite 330
25  * Boston, MA 02111-1307 USA
26  *
27  * @author Scott Ferguson
28  */

29
30 package com.caucho.ejb.entity;
31
32 import com.caucho.amber.AmberException;
33 import com.caucho.amber.entity.AmberEntityHome;
34 import com.caucho.amber.entity.EntityItem;
35 import com.caucho.amber.manager.AmberConnection;
36 import com.caucho.ejb.AbstractContext;
37 import com.caucho.ejb.AbstractServer;
38 import com.caucho.ejb.EJBExceptionWrapper;
39 import com.caucho.ejb.EjbServerManager;
40 import com.caucho.ejb.FinderExceptionWrapper;
41 import com.caucho.ejb.protocol.AbstractHandle;
42 import com.caucho.ejb.protocol.JVMObject;
43 import com.caucho.util.Alarm;
44 import com.caucho.util.CharBuffer;
45 import com.caucho.util.L10N;
46
47 import javax.ejb.EJBHome JavaDoc;
48 import javax.ejb.EJBLocalHome JavaDoc;
49 import javax.ejb.FinderException JavaDoc;
50 import javax.ejb.HomeHandle JavaDoc;
51 import javax.ejb.ObjectNotFoundException JavaDoc;
52 import javax.sql.DataSource JavaDoc;
53 import java.lang.reflect.Constructor JavaDoc;
54 import java.lang.reflect.Field JavaDoc;
55 import java.rmi.RemoteException JavaDoc;
56 import java.sql.Connection JavaDoc;
57 import java.util.ArrayList JavaDoc;
58 import java.util.Collections JavaDoc;
59 import java.util.Comparator JavaDoc;
60 import java.util.logging.Level JavaDoc;
61
62 /**
63  * EntityServer is a container for the instances of an entity bean.
64  */

65 public class EntityServer extends AbstractServer {
66   private static final L10N L = new L10N(EntityServer.class);
67   
68   private DataSource JavaDoc _dataSource;
69
70   private HomeHandle JavaDoc _homeHandle;
71   private QEntityContext _homeContext;
72
73   private int _random;
74
75   private boolean _isInit;
76   private boolean _isCMP;
77
78   private boolean _isLoadLazyOnTransaction = true;
79
80   private Field JavaDoc []_primaryKeyFields;
81
82   private Throwable JavaDoc _exception;
83
84   private AmberEntityHome _amberEntityHome;
85
86   private ArrayList JavaDoc<RemoveListener> _removeListeners =
87     new ArrayList JavaDoc<RemoveListener>();
88   private ArrayList JavaDoc<EntityServer> _updateListeners;
89
90   private Constructor JavaDoc _contextConstructor;
91
92   private boolean _isCacheable = true;
93   // private long _cacheTimeout = 5000;
94
private long _cacheTimeout = 1000;
95   private long _invalidateTime;
96
97   private int _jdbcIsolation = -1;
98   
99   /**
100    * Creates a new EntityServer.
101    *
102    * @param serverId the entity server's unique identifier
103    * @param allowJVMCall allows fast calls to the same JVM (with serialization)
104    * @param config the EJB configuration.
105    */

106   public EntityServer(EjbServerManager ejbManager)
107   {
108     super(ejbManager);
109
110     // getPersistentManager().setHome(config.getName(), this);
111
// _dataSource = config.getDataSource();
112

113     //if (_dataSource == null)
114
// _dataSource = ejbManager.getDataSource();
115
}
116
117   /**
118    * Gets the primary key class
119    */

120   public Class JavaDoc getPrimaryKeyClass()
121   {
122     return _primaryKeyClass;
123   }
124
125   /**
126    * Sets CMP.
127    */

128   public void setCMP(boolean isCMP)
129   {
130     _isCMP = isCMP;
131   }
132
133   /**
134    * Sets CMP.
135    */

136   public boolean isCMP()
137   {
138     return _isCMP;
139   }
140
141   /**
142    * Gets the persistence scheme for the entity bean
143    */

144   public boolean getBeanManagedPersistence()
145   {
146     return ! _isCMP;
147   }
148
149   /**
150    * Sets true if entities should be loaded lazily on transaction.
151    */

152   public boolean isLoadLazyOnTransaction()
153   {
154     return _isLoadLazyOnTransaction;
155   }
156
157   /**
158    * Sets true if entities should be loaded lazily on transaction.
159    */

160   public void setLoadLazyOnTransaction(boolean isLoadLazy)
161   {
162     _isLoadLazyOnTransaction = isLoadLazy;
163   }
164
165   /**
166    * Returns true if the bean should be loaded.
167    *
168    * @param loadTime the time the bean was last loaded.
169    */

170   public boolean doLoad(long loadTime)
171   {
172     if (loadTime <= _invalidateTime)
173       return true;
174
175     long expiresTime = Alarm.getCurrentTime() - _cacheTimeout;
176
177     return loadTime <= expiresTime;
178   }
179
180   /**
181    * Sets the amber entity home.
182    */

183   public void setAmberEntityHome(AmberEntityHome home)
184   {
185     _amberEntityHome = home;
186   }
187
188   /**
189    * Loads an amber entity.
190    */

191   /*
192   public EntityItem loadEntityItem(Object key)
193     throws AmberException
194   {
195     return _amberEntityHome.loadEntityItem(key);
196   }
197   */

198
199   /**
200    * Invalidates all cache entries, forcing them to reload.
201    */

202   public void invalidateCache()
203   {
204     _invalidateTime = Alarm.getCurrentTime();
205   }
206
207   /**
208    * Sets the primary key class.
209    */

210   public void setPrimaryKeyClass(Class JavaDoc cl)
211   {
212     _primaryKeyClass = cl;
213   }
214
215   /**
216    * Initializes the EntityServer, generating and compiling the skeletons
217    * if necessary.
218    */

219   public void init()
220     throws Exception JavaDoc
221   {
222     super.init();
223     
224     try {
225       // _isCacheable = _config.isCacheable();
226
//_cacheTimeout = _config.getCacheTimeout();
227

228       /*
229       if (cacheSize < 1024)
230         cacheSize = 1024;
231       */

232       
233       _jdbcIsolation = _ejbManager.getJDBCIsolation();
234       Class JavaDoc []param = new Class JavaDoc[] { EntityServer.class };
235       _contextConstructor = _contextImplClass.getConstructor(param);
236
237       _homeContext = (QEntityContext) _contextConstructor.newInstance(this);
238       
239       _localHome = _homeContext.createLocalHome();
240       _remoteHomeView = _homeContext.createRemoteHomeView();
241
242       /*
243       if (_homeStubClass != null) {
244         _remoteHomeView = _homeContext.createRemoteHomeView();
245
246     if (_config.getJndiName() != null) {
247       Context ic = new InitialContext();
248       ic.rebind(_config.getJndiName(), this);
249     }
250       }
251       */

252
253       initRelations();
254
255       if (_isCMP) {
256     // _amberEntityHome = getServerManager().getAmberEntityHome(name);
257
//_amberEntityHome = getContainer().getAmberEntityHome(_config.getName());
258
_amberEntityHome.setEntityFactory(new AmberEntityFactory(this));
259       }
260     
261       Class JavaDoc primaryKeyClass = getPrimaryKeyClass();
262
263       if (primaryKeyClass != null &&
264       ! primaryKeyClass.isPrimitive() &&
265       ! primaryKeyClass.getName().startsWith("java.lang.")) {
266     _primaryKeyFields = primaryKeyClass.getFields();
267       }
268       
269       log.config("initialized entity bean: " + this);
270     } catch (Exception JavaDoc e) {
271       _exception = e;
272
273       throw e;
274     }
275   }
276
277   /**
278    * Initialize the callbacks required to manage relations. Primarily
279    * the callbacks are need to make sure collections are updated when
280    * the target changes or is deleted.
281    */

282   private void initRelations()
283   {
284     if (! isCMP())
285       return;
286
287     /*
288     PersistentBean bean = _config.getPersistentBean();
289     ArrayList<PersistentBean> oldBeans = new ArrayList<PersistentBean>();
290     
291     Iterator iter = bean.getRelations();
292     while (iter.hasNext()) {
293       PersistentRelation rel = (PersistentRelation) iter.next();
294       PersistentBean targetBean = rel.getTargetBean();
295
296       addTargetBean(oldBeans, targetBean);
297     }
298     */

299   }
300   /*
301   private void addTargetBean(ArrayList<PersistentBean> oldBeans,
302                              PersistentBean targetBean)
303   {
304     if (! oldBeans.contains(targetBean)) {
305       oldBeans.add(targetBean);
306
307       String ejbName = targetBean.getName();
308         
309       EntityServer targetServer = (EntityServer) _ejbManager.getServer(ejbName);
310
311       if (targetServer == null) {
312         PersistentBean bean = _config.getPersistentBean();
313         throw new RuntimeException("unknown ejb `" + ejbName + "' in `" +
314                                    bean.getName() + "'");
315       }
316
317       targetServer.addRemoveListener(_homeContext);
318     }
319   }
320   */

321
322   /**
323    * Creates a handle for the primary key.
324    */

325   protected AbstractHandle createHandle(Object JavaDoc primaryKey)
326   {
327     return getHandleEncoder().createHandle(encodeId(primaryKey));
328   }
329
330   /**
331    * Returns the encoded id.
332    */

333   public String JavaDoc encodeId(Object JavaDoc primaryKey)
334   {
335     if (_primaryKeyFields == null)
336       return String.valueOf(primaryKey);
337
338     try {
339       CharBuffer cb = new CharBuffer();
340
341       for (int i = 0; i < _primaryKeyFields.length; i++) {
342         if (i != 0)
343           cb.append(',');
344
345         cb.append(_primaryKeyFields[i].get(primaryKey));
346       }
347       
348       return cb.toString();
349     } catch (IllegalAccessException JavaDoc e) {
350       throw new EJBExceptionWrapper(e);
351     }
352   }
353
354   /**
355    * Associate the skeleton with a key.
356    */

357   public void postCreate(Object JavaDoc key, QEntityContext cxt)
358   {
359     _ejbManager.putEntityIfNew(this, key, cxt);
360   }
361
362   /**
363    * Adds a remove listener.
364    *
365    * @param listener the home context that's listening for events.
366    * @param listenClass the class for this server.
367    */

368   public void addRemoveListener(QEntityContext context)
369   {
370     //removeListeners.add(new RemoveListener(listener, listenClass));
371
RemoveListener listener = null;
372     //listener = new RemoveListener(context, _config.getLocalObjectClass());
373

374     if (! _removeListeners.contains(listener))
375       _removeListeners.add(listener);
376   }
377
378   /**
379    * Adds an update listener.
380    */

381   public void addUpdateListener(EntityServer listener)
382   {
383     if (_updateListeners == null)
384       _updateListeners = new ArrayList JavaDoc<EntityServer>();
385
386     if (! _updateListeners.contains(listener))
387       _updateListeners.add(listener);
388   }
389
390   /**
391    * Remove an object.
392    */

393   public void remove(Object JavaDoc key)
394   {
395     for (int i = _removeListeners.size() - 1; i >= 0; i--) {
396       RemoveListener listener = _removeListeners.get(i);
397
398       try {
399         listener._listener._caucho_remove_callback(listener._listenClass, key);
400       } catch (Throwable JavaDoc e) {
401         EJBExceptionWrapper.createRuntime(e);
402       }
403     }
404   }
405   
406   public void removeCache(Object JavaDoc key)
407   {
408     _ejbManager.removeEntity(this, key);
409   }
410
411   /**
412    * Returns the underlying object by searching the primary key.
413    */

414   public AbstractContext findByPrimaryKey(Object JavaDoc key)
415     throws FinderException JavaDoc
416   {
417     return getContext(key, _isCMP);
418   }
419
420   /**
421    * Returns the underlying object by searching the primary key.
422    */

423   public AbstractContext findByPrimaryKey(int key)
424     throws FinderException JavaDoc
425   {
426     return getContext(new Long JavaDoc(key), _isCMP);
427   }
428
429   /**
430    * Returns the underlying object by searching the primary key.
431    *
432    * In this case, the object is known to exist.
433    */

434   public AbstractContext findKnownObjectByPrimaryKey(Object JavaDoc key)
435   {
436     // XXX:
437
if (key == null)
438       return null;
439     
440     try {
441       return getContext(key, false);
442     } catch (FinderException JavaDoc e) {
443       throw EJBExceptionWrapper.createRuntime(e);
444     }
445   }
446
447   /**
448    * Returns the underlying object by searching the primary key.
449    *
450    * In this case, the object is known to exist.
451    */

452   public Object JavaDoc findKnownObjectByPrimaryKey(int key)
453   {
454     return findKnownObjectByPrimaryKey(new Integer JavaDoc(key));
455   }
456
457   /**
458    * Returns the EJBHome stub for the container
459    */

460   public EJBHome JavaDoc getClientHome()
461     throws RemoteException JavaDoc
462   {
463     if (! _isInit) {
464       /*
465       try {
466         _config.validateJDBC();
467       } catch (ConfigException e) {
468         throw new EJBExceptionWrapper(e);
469       }
470       */

471       
472       _isInit = true;
473     }
474
475     if (_remoteHome == null) {
476       try {
477         _remoteHome = _jvmClient.createHomeStub();
478       } catch (Exception JavaDoc e) {
479         EJBExceptionWrapper.createRuntime(e);
480       }
481     }
482     
483     return _remoteHome;
484   }
485
486   /**
487    * Returns the EJBHome stub for the container
488    */

489   public EJBHome JavaDoc getEJBHome()
490     throws RemoteException JavaDoc
491   {
492     return _remoteHomeView;
493     /*
494     if (_remoteHome == null) {
495       if (! _isInit) {
496         try {
497           _config.validateJDBC();
498         } catch (ConfigException e) {
499           throw new EJBExceptionWrapper(e);
500         }
501         
502         _isInit = true;
503       }
504
505       try {
506         _remoteHome = _jvmClient.createHomeStub();
507       } catch (Exception e) {
508         EJBExceptionWrapper.createRuntime(e);
509       }
510     }
511     
512     return _remoteHome;
513     */

514   }
515
516   /**
517    * Returns the EJBLocalHome stub for the container
518    */

519   public EJBLocalHome JavaDoc getClientLocalHome()
520   {
521     if (! _isInit) {
522       /*
523       try {
524         _config.validateJDBC();
525       } catch (ConfigException e) {
526         throw new EJBExceptionWrapper(e);
527       }
528       */

529         
530       _isInit = true;
531     }
532     
533     return _localHome;
534   }
535
536   public Object JavaDoc getHomeObject()
537   {
538     if (! _isInit) {
539       /*
540       try {
541         _config.validateJDBC();
542       } catch (ConfigException e) {
543         throw new EJBExceptionWrapper(e);
544       }
545       */

546         
547       _isInit = true;
548     }
549     
550     return _remoteHomeView;
551   }
552
553   /**
554    * Creates the local stub for the object in the context.
555    */

556   /*
557   JVMObject getEJBObject(AbstractHandle handle)
558   {
559     if (remoteStubClass == null)
560       throw new IllegalStateException(L.l("`{0}' has no remote interface. Local beans must be called from a local context. Remote beans need a home and a remote interface.",
561                                           getEJBName()));
562
563     try {
564       JVMObject obj = (JVMObject) remoteStubClass.newInstance();
565       obj._init(this, handle);
566
567       return obj;
568     } catch (Exception e) {
569       throw new EJBExceptionWrapper(e);
570     }
571   }
572   */

573
574   /**
575    * Creates a handle for a new session.
576    */

577   JVMObject createEJBObject(Object JavaDoc primaryKey)
578   {
579     if (_remoteStubClass == null)
580       throw new IllegalStateException JavaDoc(L.l("`{0}' has no remote interface. Local beans must be called from a local context. Remote beans need a home and a remote interface.",
581                                           getEJBName()));
582     
583     try {
584       JVMObject obj = (JVMObject) _remoteStubClass.newInstance();
585       obj._init(this, primaryKey);
586
587       return obj;
588     } catch (Exception JavaDoc e) {
589       throw new EJBExceptionWrapper(e);
590     }
591   }
592
593   public AbstractContext getContext(Object JavaDoc key)
594     throws FinderException JavaDoc
595   {
596     return getContext(key, _isCMP);
597   }
598   
599   /**
600    * Finds the remote bean by its key.
601    *
602    * @param key the remote key
603    *
604    * @return the remote interface of the entity.
605    */

606   public AbstractContext getContext(Object JavaDoc key, boolean forceLoad)
607     throws FinderException JavaDoc
608   {
609     if (key == null)
610       return null;
611
612     QEntityContext cxt = _ejbManager.getEntity(this, key);
613
614     try {
615       if (cxt == null) {
616     EntityItem amberItem = null;
617     
618     if (_isCMP) {
619       AmberConnection aConn;
620       aConn = _amberEntityHome.getManager().getCacheConnection();
621         
622       try {
623         /*
624         amberItem = _amberEntityHome.findEntityItem(aConn,
625                             key,
626                             true);
627         */

628         // ejb/06d3
629
amberItem = _amberEntityHome.findEntityItem(aConn,
630                             key,
631                             forceLoad);
632       } catch (AmberException e) {
633         String JavaDoc name = getEJBName();
634
635         FinderException JavaDoc exn = new ObjectNotFoundException JavaDoc(L.l("'{0}' is an unknown entity.",
636                               name + "[" + key + "]"));
637         exn.initCause(e);
638         throw exn;
639       } finally {
640         aConn.freeConnection();
641       }
642     }
643
644     cxt = (QEntityContext) _contextConstructor.newInstance(this);
645     cxt.setPrimaryKey(key);
646
647     if (amberItem != null)
648       cxt.__caucho_setAmber(amberItem);
649       }
650
651       // ejb/0d33 vs ejb/0d00
652
if (forceLoad &&
653       (! _isLoadLazyOnTransaction ||
654        getTransactionManager().getTransaction() != null)) {
655     try {
656       cxt._caucho_load();
657     } catch (Exception JavaDoc e) {
658       throw e;
659     }
660       }
661
662       /*
663     try {
664       cxt._caucho_load();
665     } catch (Exception e) {
666       throw e;
667     }
668       */

669       cxt = _ejbManager.putEntityIfNew(this, key, cxt);
670
671       return cxt;
672     } catch (FinderException JavaDoc e) {
673       throw e;
674     } catch (Exception JavaDoc e) {
675       throw FinderExceptionWrapper.create(e);
676     }
677   }
678
679   /**
680    * Returns a new connection.
681    */

682   public Connection getConnection(boolean isReadOnly)
683     throws java.sql.SQLException JavaDoc
684   {
685     if (isReadOnly)
686       return _dataSource.getConnection();
687     else {
688       Connection conn = _dataSource.getConnection();
689
690       if (_jdbcIsolation > 0)
691         conn.setTransactionIsolation(_jdbcIsolation);
692
693       return conn;
694     }
695   }
696   
697   /**
698    * Updates the named entity bean
699    */

700   public void update(Object JavaDoc key)
701   {
702     if (key == null)
703       return;
704     
705     QEntityContext cxt = _ejbManager.getEntity(this, key);
706
707     if (cxt == null)
708       return;
709
710     // XXX: only update in the transaction?
711
// why doesn't the transaction have the update?
712
cxt.update();
713   }
714   
715   /**
716    * Updates the named entity bean
717    */

718   public void updateView(Object JavaDoc key)
719   {
720     if (_updateListeners == null)
721       return;
722
723     for (int i = _updateListeners.size() - 1; i >= 0; i--) {
724       EntityServer server = _updateListeners.get(i);
725
726       server.update(key);
727     }
728   }
729
730   /**
731    * Returns a random string for a new id
732    *
733    * @param max the previous maximum value
734    * @param i the repetition index
735    */

736   public String JavaDoc randomString(int max, int i)
737   {
738     return String.valueOf(++_random);
739   }
740
741   /**
742    * Returns a random integer for a new id
743    *
744    * @param max the previous maximum value
745    * @param i the repetition index
746    */

747   public int randomInt(int max, int i)
748   {
749     return max;
750   }
751
752   public static IllegalStateException JavaDoc createGetStateException(int state)
753   {
754     switch (state) {
755     case QEntity._CAUCHO_IS_REMOVED:
756       return new IllegalStateException JavaDoc(L.l("Can't access CMP field for a removed object."));
757       
758     case QEntity._CAUCHO_IS_DEAD:
759       return new IllegalStateException JavaDoc(L.l("Can't access CMP field for a dead object. The object is dead due to a runtime exception and rollback."));
760       
761     case QEntity._CAUCHO_IS_NEW:
762       return new IllegalStateException JavaDoc(L.l("Can't access CMP field for an uninitialized object."));
763       
764     case QEntity._CAUCHO_IS_HOME:
765       return new IllegalStateException JavaDoc(L.l("Can't access CMP field from a Home object."));
766
767     default:
768       return new IllegalStateException JavaDoc(L.l("Can't access CMP field from an unknown state."));
769     }
770   }
771
772   public static IllegalStateException JavaDoc createSetStateException(int state)
773   {
774     switch (state) {
775     case QEntity._CAUCHO_IS_REMOVED:
776       return new IllegalStateException JavaDoc(L.l("Can't set CMP field for a removed object."));
777       
778     case QEntity._CAUCHO_IS_DEAD:
779       return new IllegalStateException JavaDoc(L.l("Can't set CMP field for a dead object. The object is dead due to a runtime exception and rollback."));
780       
781     case QEntity._CAUCHO_IS_NEW:
782       return new IllegalStateException JavaDoc(L.l("Can't set CMP field for an uninitialized object."));
783       
784     case QEntity._CAUCHO_IS_HOME:
785       return new IllegalStateException JavaDoc(L.l("Can't set CMP field from a Home object."));
786
787     default:
788       return new IllegalStateException JavaDoc(L.l("Can't set CMP field from an unknown state."));
789     }
790   }
791   
792   /**
793    * Cleans up the entity server nicely.
794    */

795   public void destroy()
796   {
797     ArrayList JavaDoc<QEntityContext> beans = new ArrayList JavaDoc<QEntityContext>();
798
799     _ejbManager.removeBeans(beans, this);
800
801     // only purpose of the sort is to make the qa order consistent
802
Collections.sort(beans, new EntityCmp());
803     
804     for (int i = 0; i < beans.size(); i++) {
805       QEntityContext cxt = beans.get(i);
806
807       try {
808         cxt.destroy();
809       } catch (Exception JavaDoc e) {
810         log.log(Level.WARNING, e.toString(), e);
811       }
812     }
813
814     QEntityContext homeContext = _homeContext;
815     _homeContext = null;
816     
817     try {
818       if (homeContext != null)
819         homeContext.destroy();
820     } catch (Exception JavaDoc e) {
821       log.log(Level.WARNING, e.toString(), e);
822     }
823     
824     super.destroy();
825   }
826   
827   static class RemoveListener {
828     QEntityContext _listener;
829     Class JavaDoc _listenClass;
830
831     RemoveListener(QEntityContext listener, Class JavaDoc listenClass)
832     {
833       _listener = listener;
834       _listenClass = listenClass;
835     }
836
837     public boolean equals(Object JavaDoc o)
838     {
839       if (! (o instanceof RemoveListener))
840         return false;
841
842       RemoveListener listener = (RemoveListener) o;
843
844       return (_listener.equals(listener._listener) &&
845               _listenClass.equals(listener._listenClass));
846     }
847
848     public String JavaDoc toString()
849     {
850       return "RemoveListener[" + _listener + "]";
851     }
852   }
853
854   static class EntityCmp
855     implements Comparator JavaDoc<QEntityContext> {
856     public int compare(QEntityContext ca, QEntityContext cb)
857     {
858       try {
859         String JavaDoc sa = String.valueOf(ca.getPrimaryKey());
860         String JavaDoc sb = String.valueOf(cb.getPrimaryKey());
861         
862         return sa.compareTo(sb);
863       } catch (Exception JavaDoc e) {
864         return 0;
865       }
866     }
867   }
868 }
869
Popular Tags