001: /*
002: * Copyright 2006 Davide Deidda
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: /*
018: * Ammentos.java
019: *
020: * Created on 8 aprile 2005, 19.35
021: */
022:
023: package it.biobytes.ammentos;
024:
025: import it.biobytes.ammentos.util.*;
026: import it.biobytes.ammentos.query.*;
027: import it.biobytes.ammentos.validation.*;
028: import it.biobytes.ammentos.cache.*;
029: import it.biobytes.ammentos.event.*;
030: import java.util.*;
031: import java.util.logging.*;
032: import javax.sql.*;
033: import java.sql.*;
034:
035: /**
036: * This class represents the persistence framework itself.
037: * Provides all methods for managing objects persistence: save, load, lookup
038: * by primary key and delete operations. These operations can be atomic or can be
039: * performed into a transaction by using the transaction related methods such as
040: * openTransaction(), commitTransaction() and rollbackTransaction().
041: * An internal caching mechanism dramatically optimizes direct lookup() calls.
042: * Furthermore is possible to receive persistence events by registering
043: * PersistenceListeners objects to the Framework.
044: * @author Davide Deidda
045: */
046: public class Ammentos {
047:
048: private static MetadataInspector m_metadataInspector = new MetadataInspector();
049: private static Map<Class, Metadata> m_metadatas = new HashMap<Class, Metadata>();
050: private static ThreadLocal<PersistenceContext> m_context = new ThreadLocal<PersistenceContext>();
051:
052: private static EventDispatcher m_eventDispatcher = new EventDispatcher();
053:
054: private static final FrameworkState STATE_NORMAL = new NormalState();
055: private static final FrameworkState STATE_TRANSACTION = new TransactionState();
056:
057: private static PersistenceContext m_defaultContext = new PersistenceContext(
058: null);
059: private static Logger m_logger = Logger.getLogger("ammentos");
060: static {
061: Thread t = new Thread(m_eventDispatcher);
062: t.setDaemon(true);
063: t.start();
064:
065: m_defaultContext.setState(STATE_NORMAL);
066: m_logger.setLevel(Level.OFF);
067: }
068:
069: private static class NormalState extends FrameworkState {
070:
071: public <T> void doSave(Class<T> c, T obj)
072: throws PersistenceException {
073: getPersistor(c).save(c, obj);
074: m_eventDispatcher.dispatchEvent(new PersistenceEvent(obj,
075: EventType.OBJECT_SAVED));
076: currentContext().getCache().remove(obj);
077: }
078:
079: public <T> void doDelete(Class<T> c, T obj)
080: throws PersistenceException {
081: getPersistor(c).delete(c, obj);
082: m_eventDispatcher.dispatchEvent(new PersistenceEvent(obj,
083: EventType.OBJECT_DELETED));
084: currentContext().getCache().remove(obj);
085: }
086:
087: public <T> boolean doLoad(Class<T> c, T obj, Object primaryKey)
088: throws PersistenceException {
089: return getPersistor(c).load(c, obj, primaryKey);
090: }
091:
092: public <T> List<T> doLoad(Class<T> c, Query qry)
093: throws PersistenceException {
094: List<T> res = new ArrayList<T>();
095: EntityIterable<T> items = null;
096:
097: try {
098: items = loadIterable(c, qry);
099: for (T item : items) {
100: res.add(item);
101: }
102: } finally {
103: items.close();
104: }
105:
106: return res;
107: }
108:
109: public <T> EntityIterable<T> loadIterable(Class<T> c, Query qry)
110: throws PersistenceException {
111: return getPersistor(c).loadIterable(c, qry);
112: }
113:
114: @Override
115: public <T> int count(Class<T> c, Query qry)
116: throws PersistenceException {
117: return getPersistor(c).count(c, qry);
118: }
119: }
120:
121: private static class TransactionState extends FrameworkState {
122:
123: public <T> void doSave(Class<T> c, T obj)
124: throws PersistenceException {
125: getPersistor(c).save(c, obj, currentTransaction());
126: currentTransaction().involveSavedObject(obj);
127: }
128:
129: public <T> void doDelete(Class<T> c, T obj)
130: throws PersistenceException {
131: getPersistor(c).delete(c, obj, currentTransaction());
132: currentTransaction().involveDeletedObject(obj);
133: }
134:
135: public <T> boolean doLoad(Class<T> c, T obj, Object primaryKey)
136: throws PersistenceException {
137: return getPersistor(c).load(c, obj, primaryKey,
138: currentTransaction());
139: }
140:
141: public <T> List<T> doLoad(Class<T> c, Query qry)
142: throws PersistenceException {
143: List<T> res = new ArrayList<T>();
144: EntityIterable<T> items = null;
145:
146: try {
147: items = loadIterable(c, qry);
148: for (T item : items) {
149: res.add(item);
150: }
151: } finally {
152: items.close();
153: }
154: return res;
155: }
156:
157: public <T> EntityIterable<T> loadIterable(Class<T> c, Query qry)
158: throws PersistenceException {
159: return getPersistor(c).loadIterable(c, qry,
160: currentTransaction());
161: }
162:
163: @Override
164: public <T> int count(Class<T> c, Query qry)
165: throws PersistenceException {
166: return getPersistor(c).count(c, qry, currentTransaction());
167: }
168: }
169:
170: private static FrameworkState getState() {
171: FrameworkState res = currentContext().getState();
172: if (res == null) {
173: res = STATE_NORMAL;
174: currentContext().setState(res);
175: }
176: return res;
177: }
178:
179: /**
180: * Returns the metadata for the objects that belong to the provided class.
181: * @param c The class to get metadata for
182: * @throws it.biobytes.ammentos.PersistenceException If the provided class does not contain valid metadata
183: * information
184: * @return The metadata for the provided class
185: */
186: public static Metadata getMetadata(Class c)
187: throws PersistenceException {
188: if (c == null) {
189: throw new PersistenceException("Invalid class: null");
190: }
191:
192: Metadata res = m_metadatas.get(c);
193:
194: // If metadata are not present in cache
195: if (res == null) {
196: // Try loading with inspector
197: try {
198: res = m_metadataInspector.loadMetadata(c);
199: } catch (PersistenceException e) {
200: try {
201: // If the class is subclass of an entity type tries loading
202: // the same metadata
203: if (c.getAnnotation(PersistentEntity.class) == null) {
204: // Try with superclass
205: Class super Class = c.getSuperclass();
206: res = getMetadata(super Class);
207: } else {
208: throw e;
209: }
210: } catch (Exception ex) {
211: e.fillInStackTrace();
212: throw e;
213: }
214: }
215: m_metadatas.put(c, res);
216: }
217: return res;
218: }
219:
220: private static Persistor getPersistor(Class c)
221: throws PersistenceException {
222: return getMetadata(c).getPersistor();
223: }
224:
225: /**
226: * Creates a new instance of the provided object.
227: * @param c The class of the object to create
228: * @throws it.biobytes.ammentos.PersistenceException If any errors occurred while trying to create the instance
229: * @return An instance of the provided class
230: */
231: public static <T> T createInstance(Class<T> c)
232: throws PersistenceException {
233: T res;
234: try {
235: // Getting default constructor
236: java.lang.reflect.Constructor<T> constr = c
237: .getDeclaredConstructor();
238: if (constr == null) {
239: throw new PersistenceException(
240: "Default constructor not found for class " + c);
241: }
242: if (!constr.isAccessible()) {
243: constr.setAccessible(true);
244: }
245: res = constr.newInstance();
246: m_logger.info("created instance: " + res + " for class: "
247: + c);
248: } catch (PersistenceException e) {
249: throw e;
250: } catch (Exception e) {
251: throw new PersistenceException(e);
252: }
253: return res;
254: }
255:
256: /**
257: * Saves the provided object instance. If a transaction is opened this operation
258: * will be actually performed when the commitTransaction() method will be called
259: * @param obj The object to save
260: * @throws it.biobytes.ammentos.PersistenceException If any errors occurr while saving the object
261: */
262: public static <T> void save(T obj) throws PersistenceException {
263: // getState().save( ( Class<T> )obj.getClass(), obj );
264: saveDeeply((Class<T>) obj.getClass(), obj);
265: }
266:
267: /**
268: * Saves the provided object instance, regardless the object is already present
269: * into the target domain. If the object is already present the call corresponds to
270: * a normale save() method, otherwise a delete() method followed by a new save() method
271: * is called.
272: *
273: * @param obj The object to override
274: * @throws it.biobytes.ammentos.PersistenceException If any errors occurr while saving the object
275: */
276: public static <T> void override(T obj) throws PersistenceException {
277: try {
278: save(obj);
279: } catch (PersistenceException e) {
280: delete(obj);
281: save(obj);
282: }
283: }
284:
285: /**
286: * Deletes the object. If a transaction is opened this operation will be actually
287: * performed when the commitTransaction() method will be called
288: * @param obj The object to delete
289: * @throws it.biobytes.ammentos.PersistenceException
290: */
291: public static <T> void delete(T obj) throws PersistenceException {
292: //getState().delete( ( Class<T> )obj.getClass(), obj );
293: deleteDeeply((Class<T>) obj.getClass(), obj);
294: }
295:
296: /**
297: * Deletes all the objects of class c matching the provided QueryFilter.
298: * If a transaction is opened this operation will be actually
299: * performed when the commitTransaction() method will be called
300: */
301: public static <T> void delete(Class<T> c, QueryFilter filter)
302: throws PersistenceException {
303: Query qry = new Query(filter);
304: qry.setMaxResults(50);
305: List<T> objs = load(c, qry);
306:
307: while (objs.size() > 0) {
308: for (T obj : objs) {
309: delete(obj);
310: }
311: objs = load(c, qry);
312: }
313: }
314:
315: /**
316: * Loads the object whith the provided primary key
317: * @param c
318: * @param primaryKey
319: * @throws it.biobytes.ammentos.PersistenceException
320: * @return
321: */
322: public static <T> T load(Class<T> c, Object primaryKey)
323: throws PersistenceException {
324: T res = createInstance(c);
325: if (!loadDeeply(c, res, primaryKey)) {
326: res = null;
327: }
328: return res;
329: }
330:
331: /**
332: * This method represents a shortcut for loading objects with queries when the
333: * expected return value is only one object. A persistenceException will
334: * be thrown if the query returns more than one value.
335: *
336: * @param c
337: * @param query
338: * @return
339: * @throws it.biobytes.ammentos.PersistenceException
340: */
341: public static <T> T loadUnique(Class<T> c, Query query)
342: throws PersistenceException {
343: T res = null;
344: List<T> objs = load(c, query);
345: if (objs.size() > 1) {
346: throw new PersistenceException(
347: "Not unique result for provided query");
348: } else if (objs.size() > 0) {
349: res = objs.get(0);
350: }
351: return res;
352: }
353:
354: /**
355: * This method represents a shortcut for loading objects with queries when the
356: * expected return value is only one object. A persistenceException will
357: * be thrown if the query returns more than one value.
358: *
359: * @param c
360: * @param query
361: * @return
362: * @throws it.biobytes.ammentos.PersistenceException
363: */
364: public static <T> T loadUnique(Class<T> c, QueryFilter filter)
365: throws PersistenceException {
366: return loadUnique(c, new Query(filter));
367: }
368:
369: /**
370: * Loads the object with the provided composite primary key. Values must be
371: * passed in the same order as in PersistentEntity declaration.
372: *
373: *
374: */
375: public static <T> T load(Class<T> c, Object... primaryKeys)
376: throws PersistenceException {
377: T res = null;
378: try {
379: Field[] keys = getMetadata(c).getPrimaryKeyFields();
380: if (keys.length != primaryKeys.length) {
381: throw new Exception();
382: }
383: Query qry = new Query();
384:
385: for (int i = 0; i < primaryKeys.length; i++) {
386: SimpleQueryFilter filter = new SimpleQueryFilter(
387: keys[i].getName());
388: filter.setObject(primaryKeys[i], keys[i].getType());
389: qry.appendFilter(filter);
390: }
391:
392: List<T> objs = load(c, qry);
393: if (objs != null && objs.size() > 0) {
394: res = objs.get(0);
395: }
396: } catch (PersistenceException ex) {
397: throw ex;
398: } catch (Exception ex) {
399: throw new PersistenceException(
400: "Invalid composite primary key. Please check number and types of parameters");
401: }
402: return res;
403: }
404:
405: /**
406: * Loads the objects which match the provided query. The returned list is
407: * unmodifiabale.
408: * @param c
409: * @param qry
410: * @throws it.biobytes.ammentos.PersistenceException
411: * @return
412: */
413: public static <T> List<T> load(Class<T> c, Query qry)
414: throws PersistenceException {
415: //return Collections.unmodifiableList(doLoad(c, qry));
416: return doLoad(c, qry);
417: }
418:
419: /**
420: * Loads the objects which match the provided query.
421: *
422: * @param c
423: * @param filter
424: * @throws it.biobytes.ammentos.PersistenceException
425: * @return
426: */
427: public static <T> List<T> load(Class<T> c, QueryFilter filter)
428: throws PersistenceException {
429: //return Collections.unmodifiableList(doLoad(c, new Query(filter)));
430: return doLoad(c, new Query(filter));
431: }
432:
433: /**
434: * Counts the objects which match the provided query.
435: *
436: * @param c
437: * @param qry
438: * @return
439: * @throws it.biobytes.ammentos.PersistenceException
440: */
441: public static <T> int count(Class<T> c, Query qry)
442: throws PersistenceException {
443: return getState().count(c, qry);
444: }
445:
446: /**
447: * Counts the objects which match the provided queryfilter.
448: *
449: * @param c
450: * @param filter
451: * @return
452: * @throws it.biobytes.ammentos.PersistenceException
453: */
454: public static <T> int count(Class<T> c, QueryFilter filter)
455: throws PersistenceException {
456: return getState().count(c, new Query(filter));
457: }
458:
459: /**
460: * Returns an updatable list of objects which match the provided query. By
461: * calling add() and remove() method on this list the objects will be
462: * respectively saved or removed from their persistor.
463: */
464: public static <T> List<T> loadUpdatable(Class<T> c, Query qry)
465: throws PersistenceException {
466: return new UpdatableList(doLoad(c, qry));
467: }
468:
469: public static <T> Iterable<T> iterate(Class<T> c, Query qry)
470: throws PersistenceException {
471: if (currentContext().getIterable() != null) {
472: throw new PersistenceException(
473: "Iterable already opened in current context, Please call closeIteration() first.");
474: }
475: EntityIterable<T> res = getState().loadIterable(c, qry);
476: currentContext().setIterable(res);
477: return res;
478: }
479:
480: public static void closeIteration() {
481: EntityIterable eit = currentContext().getIterable();
482: if (eit != null) {
483: eit.close();
484: currentContext().setIterable(null);
485: }
486: }
487:
488: /**
489: * Performs the load operation for the provided query, returning a normal
490: * list
491: */
492: private static <T> List<T> doLoad(Class<T> c, Query qry)
493: throws PersistenceException {
494: // First loading objects as "c" items
495: List<T> res = getState().load(c, qry);
496: return res;
497: }
498:
499: /**
500: * Gets the primary key field for the objects of the provided class
501: * @param c
502: * @throws it.biobytes.ammentos.PersistenceException
503: * @return
504: */
505: public static Field getPrimaryKeyField(Class c)
506: throws PersistenceException {
507: return getMetadata(c).getPrimaryKeyField();
508: }
509:
510: private static Field getSuperKeyField(Class c)
511: throws PersistenceException {
512: return getMetadata(c).getSuperKeyField();
513: }
514:
515: /**
516: * Lookup the object of the provided class whith the specified primary key.
517: * This methods uses an internal caching mechanism which dramatically increases
518: * the performance, so use this method instead of load(Class,String) whenever
519: * is possible to obtain the best performance in retrieving objects.
520: * @param c
521: * @param primaryKey
522: * @throws it.biobytes.ammentos.PersistenceException
523: * @return
524: */
525: public static <T> T lookup(Class<T> c, Object primaryKey)
526: throws PersistenceException {
527: T res = null;
528:
529: res = currentContext().getCache().lookup(c, primaryKey);
530: if (res == null) {
531: res = load(c, primaryKey);
532: if (res != null) {
533: currentContext().getCache().store(res);
534: }
535: }
536:
537: return res;
538: }
539:
540: /**
541: * Sets the default datasource for this framework
542: * @param source
543: */
544: public static void setDataSource(DataSource source) {
545: m_defaultContext.setDataSource(source);
546: }
547:
548: /**
549: * Returns the default datasource for this framework
550: *
551: * @return The default datasource
552: */
553: public static DataSource getDataSource() {
554: return m_defaultContext.getDataSource();
555: }
556:
557: /**
558: * Opens a transaction to the datatabse. Each next call to save() method in
559: * the same thread will save objects into this transaction, until one between
560: * commitTransaction() or rollbackTransaction() is called.
561: *
562: * The isolation level for the created transaction is TRANSACTION_READ_UNCOMMITTED,
563: * what means that dirty reads are allowed.
564: */
565: public static void openTransaction() throws PersistenceException {
566: openTransaction(Connection.TRANSACTION_READ_UNCOMMITTED);
567: }
568:
569: /**
570: * Opens a transaction to the datatabse. Each next call to save() method in
571: * the same thread will save objects into this transaction, until one between
572: * commitTransaction() or rollbackTransaction() is called.
573: *
574: * The isolation level for the created transaction must be one between the
575: * constants defined for java.sql.Connection.
576: */
577: public static void openTransaction(int isolationLevel)
578: throws PersistenceException {
579: currentContext().setTransaction(
580: new Transaction(isolationLevel, currentContext()));
581: currentContext().setState(STATE_TRANSACTION);
582: }
583:
584: /**
585: * Rolls back the current transaction. The cache of the objects involved in
586: * the transaction will not be removed.
587: */
588: public static void rollbackTransaction()
589: throws PersistenceException {
590: try {
591: currentTransaction().rollback();
592: } catch (Exception e) {
593: throw new PersistenceException(e);
594: }
595:
596: currentContext().setTransaction(null);
597: currentContext().setState(STATE_NORMAL);
598: }
599:
600: /**
601: * Commits the current transaction. If the commit ends successfully all the
602: * objects involved in the transaction are removed from the cache.
603: */
604: public static void commitTransaction() throws PersistenceException {
605: try {
606: currentTransaction().commit();
607: for (Object obj : currentTransaction().savedObjects()) {
608: m_eventDispatcher.dispatchEvent(new PersistenceEvent(
609: obj, EventType.OBJECT_SAVED));
610: currentContext().getCache().remove(obj);
611: }
612:
613: for (Object obj : currentTransaction().deletedObjects()) {
614: m_eventDispatcher.dispatchEvent(new PersistenceEvent(
615: obj, EventType.OBJECT_DELETED));
616: currentContext().getCache().remove(obj);
617: }
618: } catch (Exception e) {
619: throw new PersistenceException(e);
620: }
621:
622: currentContext().setTransaction(null);
623: currentContext().setState(STATE_NORMAL);
624: }
625:
626: /**
627: * Returns a connection to the underlying database. The connection will belong
628: * to the currently used persistence context
629: *
630: * @throws it.biobytes.ammentos.PersistenceException
631: * @return
632: */
633: public static Connection getDbConnection()
634: throws PersistenceException {
635: Connection res = null;
636: try {
637: if (m_context.get() == null) {
638: res = m_defaultContext.getDataSource().getConnection();
639: } else {
640: res = m_context.get().getDataSource().getConnection();
641: }
642: m_logger.info("Returning connection: " + res);
643: } catch (SQLException e) {
644: throw new PersistenceException(e);
645: }
646: return res;
647: }
648:
649: /**
650: * Validates the provided Object using its related validator
651: * @param obj
652: * @throws it.biobytes.ammentos.PersistenceException
653: * @return
654: */
655: public static <T> void validate(Class<T> c, T obj)
656: throws PersistenceException {
657: Validator validator = getMetadata(c).getValidator();
658: ValidationReport report = null;
659: if (validator != null) {
660: report = validator.validate(obj);
661: }
662:
663: if ((report != null) && !report.isEmpty()) {
664: throw new PersistenceException(report.getErrors()[0]);
665: }
666: }
667:
668: /**
669: * Returns a "diff" report for the provided objects
670: */
671: public static <T> List<Field> diff(T obj1, T obj2)
672: throws PersistenceException {
673: List<Field> res = new ArrayList<Field>();
674: diffDeeply(res, (Class<T>) obj1.getClass(), obj1, obj2);
675: return res;
676: }
677:
678: /**
679: * Similarly to java.lang.String.intern() method, returns the internal, cached
680: * version of the provided object, if present; otherwise returns the same
681: * object and puts the provided one into the cache.
682: */
683: public static <T> T intern(T obj) throws PersistenceException {
684: T res = obj;
685: if (obj != null) {
686: Class<T> c = (Class<T>) obj.getClass();
687: Object pKey = getPrimaryKeyField(c).get(obj);
688: T cachedObj = currentContext().getCache().lookup(c, pKey);
689: if (cachedObj != null) {
690: res = cachedObj;
691: } else {
692: currentContext().getCache().store(obj);
693: }
694: }
695: return res;
696: }
697:
698: /**
699: * Adds a PersistenceListener to the framework.
700: * @param listener
701: */
702: public static void addPersistenceListener(
703: PersistenceListener listener) {
704: m_eventDispatcher.addPersistenceListener(listener);
705: }
706:
707: public static void removePersistenceListener(
708: PersistenceListener listener) {
709: m_eventDispatcher.removePersistenceListener(listener);
710: }
711:
712: private static <T> void saveDeeply(Class<T> c, T obj)
713: throws PersistenceException {
714: Class[] parents = getInheritancePath(c);
715: // Superclasses are saved in top down way
716: for (int i = parents.length - 1; i >= 0; i--) {
717: Class currentClass = parents[i];
718: validate(currentClass, obj);
719: getState().save(currentClass, obj);
720: }
721: }
722:
723: private static <T> void deleteDeeply(Class<T> c, T obj)
724: throws PersistenceException {
725: Class[] parents = getInheritancePath(c);
726: // Superclasses are deleted in down top way
727: for (int i = 0; i < parents.length; i++) {
728: Class currentClass = parents[i];
729: getState().delete(currentClass, obj);
730: }
731: }
732:
733: protected static <T> boolean loadDeeply(Class<T> c, T obj,
734: Object primaryKey) throws PersistenceException {
735: boolean res = true;
736: Class[] parents = getInheritancePath(c);
737:
738: // Superclasses are loaded in down top way
739: Object key = primaryKey;
740: for (int i = 0; i < parents.length; i++) {
741: Class currentClass = parents[i];
742: res = getState().load(currentClass, obj, key);
743: key = getSuperKeyField(currentClass).get(obj);
744: if (!res) {
745: break;
746: }
747: }
748:
749: return res;
750: }
751:
752: private static <T> void diffDeeply(List<Field> res, Class<T> c,
753: T obj1, T obj2) throws PersistenceException {
754: Class[] parents = getInheritancePath(c);
755: for (Class currentClass : parents) {
756: res.addAll(Diff.diff(currentClass, obj1, obj2));
757: }
758: }
759:
760: /**
761: * Returns the inheritance path of an object, no matter if the object's
762: * declaring class is persistence or not. This allows non-persistent objects
763: * to be persisted even if they are just subclasses of persistent classes.
764: */
765: private static Class[] getInheritancePath(Class c)
766: throws PersistenceException {
767: return getMetadata(c).inheritancePath();
768: }
769:
770: protected static void openContext(PersistenceContext context)
771: throws PersistenceException {
772: if (m_context.get() != null) {
773: throw new PersistenceException(
774: "Context already set in the current thread. Please close current context before calling this method");
775: }
776: m_context.set(context);
777: }
778:
779: protected static void closeContext() {
780: m_context.set(null);
781: }
782:
783: public static PersistenceContext createContext(DataSource source) {
784: return new PersistenceContext(source);
785: }
786:
787: public static PersistenceContext currentContext() {
788: PersistenceContext res = null;
789: res = m_context.get();
790: if (res == null) {
791: res = m_defaultContext;
792: }
793: return res;
794: }
795:
796: public static Transaction currentTransaction() {
797: return currentContext().getTransaction();
798: }
799: }
|