001/*
002 $Id: HibernateUtil.java 4440 2013-03-12 11:37:46Z olle $
003
004 Copyright (C) 2006, 2007 Gregory Vincic, Olle Mansson
005
006 This file is part of Proteios.
007 Available at http://www.proteios.org/
008
009 Proteios is free software; you can redistribute it and/or modify it
010 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 Proteios is distributed in the hope that it will be useful, but
015 WITHOUT ANY WARRANTY; without even the implied warranty of
016 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
017 General Public License for more details.
018
019 You should have received a copy of the GNU General Public License
020 along with this program; if not, write to the Free Software
021 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
022 02111-1307, USA.
023 */
024package org.proteios.core;
025
026import org.hibernate.EntityMode;
027import org.hibernate.Filter;
028import org.hibernate.FlushMode;
029import org.hibernate.Hibernate;
030import org.hibernate.HibernateException;
031import org.hibernate.LockOptions;
032import org.hibernate.Query;
033import org.hibernate.SQLQuery;
034import org.hibernate.ScrollMode;
035import org.hibernate.ScrollableResults;
036import org.hibernate.Session;
037import org.hibernate.SessionFactory;
038import org.hibernate.StaleStateException;
039import org.hibernate.StatelessSession;
040import org.hibernate.Transaction;
041import org.hibernate.cfg.Configuration;
042import org.hibernate.cfg.Mappings;
043import org.hibernate.dialect.Dialect;
044import org.hibernate.dialect.Oracle9iDialect;
045import org.hibernate.engine.Mapping;
046import org.hibernate.exception.ConstraintViolationException;
047import org.hibernate.mapping.PersistentClass;
048import org.hibernate.mapping.Property;
049import org.hibernate.mapping.SimpleValue;
050import org.hibernate.mapping.Table;
051import org.hibernate.metadata.ClassMetadata;
052import org.hibernate.tool.hbm2ddl.SchemaExport;
053import org.hibernate.tool.hbm2ddl.SchemaUpdate;
054import org.proteios.core.data.BasicData;
055//import org.proteios.core.data.ExtendableData;
056import org.proteios.core.data.NewsData;
057import org.proteios.core.data.OwnableData;
058import org.proteios.core.data.RemovableData;
059import org.proteios.core.data.ShareableData;
060import org.proteios.core.query.QueryType;
061
062import java.net.URI;
063import java.sql.Connection;
064import java.sql.DatabaseMetaData;
065import java.sql.ResultSet;
066import java.sql.SQLException;
067import java.util.Date;
068import java.util.Iterator;
069import java.util.List;
070
071/**
072 * This class collects most of the functionality we need for using Hibernate.
073 * 
074 * @author Nicklas, Samuel
075 * @version 2.0
076 * @base.modified $Date: 2013-03-12 12:37:46 +0100 (Tue, 12 Mar 2013) $
077 */
078public class HibernateUtil
079{
080        /**
081         * Log core events.
082         */
083        private static final org.apache.log4j.Logger log = org.apache.log4j.LogManager
084                .getLogger("org.proteios.core");
085        /**
086         * Log all batcher SQL statements.
087         */
088        private static final org.apache.log4j.Logger logSql = org.apache.log4j.LogManager
089                .getLogger("org.proteios.core.batcher.sql");
090        /**
091         * The single SessionFactory.
092         */
093        private static SessionFactory sf = null;
094        /**
095         * The Hibernate configuration, including all mappings.
096         */
097        private static Configuration cfg = null;
098        /**
099         * The mappings related to a configuration.
100         */
101        private static Mappings mappings = null;
102        /**
103         * The Hibernate database dialect in use.
104         */
105        private static Dialect dialect = null;
106        /**
107         * If ANSI or theta joins should be used.
108         */
109        private static boolean useThetaJoin = false;
110        private static boolean isInitialised = false;
111
112
113        /**
114         * Initialise this class. This is done at startup time by the
115         * {@link Application#start()} method. Initialising means that we load the
116         * configuration from the properties and the xml file, read all mapping
117         * files, generate additional mappings for the {@link ExtendableData} items,
118         * and generate filters used by {@link Query} implementation.
119         */
120        static synchronized void init()
121                        throws BaseException
122        {
123                // Return if we have already been initialised
124                if (isInitialised)
125                        return;
126                try
127                {
128                        cfg = new Configuration();
129                        setConfigurationProperties(cfg);
130                        cfg.configure();
131                        addStaticMappings(cfg);
132                        mappings = cfg.createMappings();
133                        cfg.buildMappings();
134                        addExtendedPropertiesMappings(cfg, mappings);
135                        addFilterConditions();
136                        sf = cfg.buildSessionFactory();
137                        dialect = Dialect.getDialect(cfg.getProperties());
138                        useThetaJoin = dialect instanceof Oracle9iDialect;
139                }
140                catch (HibernateException ex)
141                {
142                        throw new BaseException(ex);
143                }
144                isInitialised = true;
145        }
146
147
148        /**
149         * Unload all settings.
150         */
151        static synchronized void unload()
152        {
153                isInitialised = false;
154                if (sf != null && !sf.isClosed()) sf.close();
155                sf = null;
156                cfg = null;
157                mappings = null;
158                dialect = null;
159        }
160
161
162        /**
163         * Read database configuration properties from the base.config file and add
164         * those to the Hibernate configuration.
165         */
166        private static void setConfigurationProperties(Configuration cfg)
167                        throws BaseException
168        {
169                assert cfg != null : "cfg == null";
170                cfg.setProperty("hibernate.dialect", Config.getString("db.dialect"));
171                cfg.setProperty("hibernate.connection.driver_class", Config
172                        .getString("db.driver"));
173                cfg.setProperty("hibernate.connection.url", Config.getString("db.url"));
174                cfg.setProperty("hibernate.connection.username", Config
175                        .getString("db.username"));
176                cfg.setProperty("hibernate.connection.password", Config
177                        .getString("db.password"));
178        }
179
180
181        /**
182         * Read all mapping files, which must be named <code>Class.hbm.xml</code>
183         * where class is the name of each class. The must be located in the same
184         * directory as the class files, and we support loading the xml files both
185         * from a jar file of from the directory.
186         */
187        private static void addStaticMappings(Configuration cfg)
188                        throws BaseException
189        {
190                assert cfg != null : "cfg == null";
191                try
192                {
193                        String classPath = HibernateUtil.class.getResource(
194                                "HibernateUtil.class").getPath();
195                        int index = classPath.indexOf("!");
196                        if (index == -1)
197                        {
198                                // We are not in a JAR, load configuration from the directory
199                                // instead
200                                cfg
201                                        .addDirectory((new java.io.File(new URI(classPath)
202                                                .getPath())).getParentFile());
203                        }
204                        else
205                        {
206                                // We get a path like:
207                                // file:/usr/local/.../ProteiosCore.jar!org/proteios....
208                                // Remove everything after the "!" to get the path
209                                // to this JAR, which contains all XML mapping files.
210                                cfg.addJar(new java.io.File(new URI(classPath.substring(0,
211                                        index)).getPath()));
212                        }
213                }
214                catch (Exception ex)
215                {
216                        log.error("Exception while adding Static Mappings", ex);
217                        throw new BaseException(ex);
218                }
219        }
220
221
222        /**
223         * Generate additional mappings for all {@link ExtendableData} items.
224         */
225        private static void addExtendedPropertiesMappings(Configuration cfg, Mappings mappings)
226        {
227                List<String> classes = ExtendedProperties.getClasses();
228                String packageName = BasicData.class.getPackage().getName();
229                for (String className : classes)
230                {
231                        PersistentClass pc = cfg
232                                .getClassMapping(packageName + "." + className);
233                        List<ExtendedProperty> properties = ExtendedProperties
234                                .getProperties(className);
235                        for (ExtendedProperty ep : properties)
236                        {
237                                log
238                                        .info("Adding extended property " + ep.getName() + " to class " + className);
239                                pc.addProperty(createExtendedProperty(mappings, pc.getTable(), ep, true));
240                        }
241                }
242        }
243
244
245        /**
246         * Add an extended property to the specified Hibernate mapping
247         * 
248         * @param t The table the property should be added to
249         * @param ep The extended property to add to the mapping
250         * @param extendable TRUE if the property belongs to an extendable item,
251         *        FALSE otherwise
252         */
253        private static Property createExtendedProperty(Mappings mappings, Table t,
254                        ExtendedProperty ep, boolean extendable)
255        {
256                org.hibernate.mapping.Column c = new org.hibernate.mapping.Column();
257                c.setName("`" + ep.getColumn() + "`");
258                c.setNullable(ep.isNullable());
259                c.setLength(ep.getLength());
260                t.addColumn(c);
261                SimpleValue v = new SimpleValue(mappings, t);
262                v.addColumn(c);
263                v.setTypeName(ep.getType().getTypeWrapper().getHibernateType().getName());
264                Property p = new Property();
265                p.setValue(v);
266                p.setName(ep.getName());
267                p.setNodeName(ep.getName());
268                if (extendable)
269                        p
270                                .setPropertyAccessorName("org.proteios.core.ExtendedPropertyAccessor");
271                p.setCascade("none");
272                p.setUpdateable(ep.isUpdateable());
273                p.setInsertable(ep.isInsertable());
274                return p;
275        }
276
277
278        /**
279         * Generate filters for {@link Ownable}, {@link Shareable}, etc. classes
280         * that are used by the {@link Query} implementations. We are generating the
281         * filters here because we don't want anyone to mess them which could have
282         * been the case if the filter had been defined in the xml mapping files.
283         */
284        @SuppressWarnings(
285                {
286                        "unchecked"
287                })
288        private static void addFilterConditions()
289                        throws BaseException
290        {
291                assert cfg != null : "cfg == null";
292                Iterator<PersistentClass> iterator = cfg.getClassMappings();
293                while (iterator.hasNext())
294                {
295                        PersistentClass pc = iterator.next();
296                        Class c = pc.getMappedClass();
297                        log.debug("Adding 'denyAll' filter to " + c.getName());
298                        pc.addFilter("denyAll", "`id` != `id`");
299                        if (NewsData.class.isAssignableFrom(c))
300                        {
301                                log.debug("Adding 'todaysNews' filter to " + c.getName());
302                                pc
303                                        .addFilter("todaysNews",
304                                                ":today >= `start_date` AND (:today <= `end_date` OR `end_date` IS NULL)");
305                        }
306                        if (RemovableData.class.isAssignableFrom(c))
307                        {
308                                log.debug("Adding 'isRemoved' filter to " + c.getName());
309                                pc.addFilter("isRemoved", ":removed = `removed`");
310                        }
311                        if (OwnableData.class.isAssignableFrom(c))
312                        {
313                                log.debug("Adding 'ownedBy' filter to " + c.getName());
314                                pc.addFilter("ownedBy", ":owner = `owner`");
315                                log.debug("Adding 'notOwnedBy' filter to " + c.getName());
316                                pc.addFilter("notOwnedBy", ":owner != `owner`");
317                                log.debug("Adding 'memberOf' filter to " + c.getName());
318                                pc.addFilter("memberOf",
319                                        ":owner != `owner` AND `id` IN (:items)");
320                                log
321                                        .debug("Adding 'ownedByOrMemberOf' filter to " + c
322                                                .getName());
323                                pc.addFilter("ownedByOrMemberOf",
324                                        "(:owner = `owner` OR `id` IN (:items))");
325                        }
326                        else
327                        {
328                                pc.addFilter("memberOf", "`id` IN (:items)");
329                                log.debug("Adding 'memberOf' filter to " + c.getName());
330                        }
331                        if (ShareableData.class.isAssignableFrom(c))
332                        {
333                                log.debug("Adding 'sharedTo' filter to " + c.getName());
334                                pc.addFilter("sharedTo",
335                                        ":owner != `owner` AND `itemkey_id` IN (:itemKeys)");
336                                log.debug("Adding 'inProject' filter to " + c.getName());
337                                pc.addFilter("inProject", "`projectkey_id` IN (:projectKeys)");
338                                log
339                                        .debug("Adding 'ownedByOrSharedTo' filter to " + c
340                                                .getName());
341                                pc.addFilter("ownedByOrSharedTo",
342                                        "(:owner = `owner` OR `itemkey_id` IN (:itemKeys))");
343                                log.debug("Adding 'ownedByOrInProject' filter to " + c
344                                        .getName());
345                                pc.addFilter("ownedByOrInProject",
346                                        "(:owner = `owner` OR `projectkey_id` IN (:projectKeys))");
347                                log.debug("Adding 'sharedToOrInProject' filter to " + c
348                                        .getName());
349                                pc
350                                        .addFilter(
351                                                "sharedToOrInProject",
352                                                "((:owner != `owner` AND `itemkey_id` IN (:itemKeys)) OR `projectkey_id` IN (:projectKeys))");
353                                log
354                                        .debug("Adding 'ownedByOrSharedToOrInProject' filter to " + c
355                                                .getName());
356                                pc
357                                        .addFilter(
358                                                "ownedByOrSharedToOrInProject",
359                                                "(:owner = `owner` OR `itemkey_id` IN (:itemKeys) OR `projectkey_id` IN (:projectKeys))");
360                        }
361                }
362        }
363
364
365        /**
366         * Create the all tables in the database. We use the {@link SchemaUpdate}
367         * tool provided by Hibernate. The database must exists and we recommend
368         * that it is empty to begin with.
369         */
370        static void createStaticTables(boolean update)
371                        throws BaseException
372        {
373                assert sf != null : "HibernateUtil has not been initialised";
374                if (!update && !isEmptyDatabase())
375                {
376                        throw new BaseException("Database already has tables.");
377                }
378                try
379                {
380                        if (update)
381                        {
382                                // TODO - This will not create indexes, it must be done manually
383                                SchemaUpdate se = new SchemaUpdate(cfg);
384                                se.execute(false, true);
385                        }
386                        else
387                        {
388                                SchemaExport se = new SchemaExport(cfg);
389                                se.execute(false, true, false, true);
390                        }
391                }
392                catch (HibernateException ex)
393                {
394                        throw new BaseException(ex);
395                }
396        }
397
398
399        /**
400         * Check if the main database has any tables.
401         * 
402         * @return TRUE if there are any tables, FALSE otherwise
403         */
404        @SuppressWarnings(
405                {
406                        "unchecked", "deprecation"
407                }) // "deprecation"  Added here until hibernate 4.x where .connection() method should be replaced
408        static boolean isEmptyDatabase()
409                        throws BaseException
410        {
411                Session session = null;
412                boolean isEmpty = true;
413                try
414                {
415                        session = HibernateUtil.newSession();
416                        DatabaseMetaData metaData = session.connection().getMetaData();
417                        Iterator<PersistentClass> classes = cfg.getClassMappings();
418                        while (classes.hasNext() && isEmpty)
419                        {
420                                PersistentClass pClass = classes.next();
421                                Table t = pClass.getTable();
422                                ResultSet tables = metaData.getTables(t.getCatalog(), t
423                                        .getSchema(), t.getName(), new String[]
424                                        {
425                                                "TABLE"
426                                        });
427                                if (tables.next())
428                                {
429                                        isEmpty = false;
430                                        log
431                                                .error("Table '" + t.getName() + "' already exists; install aborted");
432                                }
433                                tables.close();
434                        }
435                        HibernateUtil.close(session);
436                        session = null;
437                }
438                catch (SQLException ex)
439                {
440                        throw new BaseException(ex);
441                }
442                catch (HibernateException ex)
443                {
444                        throw new BaseException(ex);
445                }
446                finally
447                {
448                        if (session != null)
449                                HibernateUtil.close(session);
450                }
451                return isEmpty;
452        }
453
454
455        /**
456         * Create a new Hibernate session.
457         */
458 @SuppressWarnings("deprecation") // Added here until hibernate 4.x where .connection() method should be replaced
459        static Session newSession()
460                        throws BaseException
461        {
462                assert sf != null : "HibernateUtil has not been initialised";
463                try
464                {
465                        Session session = sf.openSession();
466                        // FlushMode.COMMIT means that all changes
467                        // are automatically flushed before a commit is performed
468                        session.setFlushMode(FlushMode.COMMIT);
469                        /*
470                         * If you get an exception for setTransactionIsolation it may be a
471                         * problem of a pooled connection that wasn't commited or rollbacked
472                         * by the previous user. The following command can fix it:
473                         * session.connection().rollback();
474                         * ==================================================================
475                         * BUT!!! TRY TO FIND THE PLACE WHERE THE TRANSACTION IS LEFT
476                         * OPEN!!!
477                         * ==================================================================
478                         */
479                        // Important: we may get into problem with Keys otherwise which do
480                        // their work
481                        // in separate transactions
482                        session.connection().setTransactionIsolation(
483                                Connection.TRANSACTION_READ_COMMITTED);
484                        return session;
485                }
486                catch (HibernateException ex)
487                {
488                        throw new BaseException(ex);
489                }
490                catch (SQLException ex)
491                {
492                        throw new BaseException(ex);
493                }
494        }
495
496 @SuppressWarnings("deprecation") // Added here until hibernate 4.x where .connection() method should be replaced
497        static StatelessSession newStatelessSession(Session session)
498                        throws BaseException
499        {
500                assert sf != null : "HibernateUtil has not been initialised";
501                assert session != null : "session == null";
502                try
503                {
504                        return sf.openStatelessSession(session.connection());
505                }
506                catch (HibernateException ex)
507                {
508                        throw new BaseException(ex);
509                }
510        }
511
512
513        /**
514         * Create a new transaction for a session. If a transaction has already been
515         * created for the session, that one is returned.
516         */
517        static Transaction newTransaction(Session session)
518                        throws BaseException
519        {
520                assert session != null : "session == null";
521                try
522                {
523                        return session.beginTransaction();
524                }
525                catch (HibernateException ex)
526                {
527                        throw new BaseException(ex);
528                }
529        }
530
531
532        /**
533         * Commit a transaction.
534         */
535        static void commit(Transaction tx)
536                        throws BaseException
537        {
538                assert tx != null : "tx == null";
539                try
540                {
541                        tx.commit();
542                }
543                catch (ConstraintViolationException ex)
544                {
545                        // This is the best we can do... I think...
546                        throw new DatabaseException(ex.getSQLException());
547                }
548                catch (StaleStateException ex)
549                {
550                        throw new ItemModifiedException(ex);
551                }
552                catch (HibernateException ex)
553                {
554                        throw new BaseException(ex);
555                }
556        }
557
558
559        /**
560         * Rollback a transaction.
561         */
562        static void rollback(Transaction tx)
563                        throws BaseException
564        {
565                assert tx != null : "tx == null";
566                try
567                {
568                        tx.rollback();
569                }
570                catch (HibernateException ex)
571                {
572                        throw new BaseException(ex);
573                }
574        }
575
576
577        /**
578         * Close a session.
579         */
580        static void close(Session session)
581        {
582                assert session != null : "session == null";
583                try
584                {
585                        session.close();
586                }
587                catch (HibernateException ex)
588                {
589                        log.error("Exception when closing session", ex);
590                }
591        }
592
593
594        /**
595         * Close a statless session.
596         */
597        static void close(StatelessSession session)
598        {
599                assert session != null : "session == null";
600                try
601                {
602                        session.close();
603                }
604                catch (HibernateException ex)
605                {
606                        log.error("Exception when closing session", ex);
607                }
608        }
609
610
611        /**
612         * Flush all changed objects to the database.
613         * 
614         * @see #clear(Session)
615         */
616        static void flush(Session session)
617                        throws BaseException
618        {
619                assert session != null : "session == null";
620                try
621                {
622                        session.flush();
623                }
624                catch (HibernateException ex)
625                {
626                        throw new BaseException(ex);
627                }
628        }
629
630
631        /**
632         * Clear the session of cached objects. Changes that has not been written to
633         * the database are lost.
634         * 
635         * @see #flush(Session)
636         */
637        static void clear(Session session)
638                        throws BaseException
639        {
640                assert session != null : "session == null";
641                try
642                {
643                        session.clear();
644                }
645                catch (HibernateException ex)
646                {
647                        throw new BaseException(ex);
648                }
649        }
650
651
652        /**
653         * Save a new data object to the database.
654         */
655        static void saveData(Session session, BasicData data)
656                        throws BaseException
657        {
658                assert session != null : "session == null";
659                assert data != null : "data == null";
660                try
661                {
662                        session.save(data);
663                }
664                catch (ConstraintViolationException ex)
665                {
666                        // This is the best we can do... I think...
667                        DatabaseException e = new DatabaseException(ex.getSQLException());
668                        String entityName = data.getClass().getName();
669                        ClassMetadataExposed cme = new ClassMetadataExposed(HibernateUtil
670                                .getClassMetadata(entityName));
671                        e.setMetaData(cme);
672                        throw e;
673                }
674                catch (HibernateException ex)
675                {
676                        throw new BaseException(ex);
677                }
678        }
679
680
681        /**
682         * Save a new data object to the database.
683         */
684        static void saveData(StatelessSession session, BasicData data)
685                        throws BaseException
686        {
687                assert session != null : "session == null";
688                assert data != null : "data == null";
689                try
690                {
691                        session.insert(data);
692                }
693                catch (ConstraintViolationException ex)
694                {
695                        // This is the best we can do... I think...
696                        DatabaseException e = new DatabaseException(ex.getSQLException());
697                        String entityName = data.getClass().getName();
698                        ClassMetadataExposed cme = new ClassMetadataExposed(HibernateUtil
699                                .getClassMetadata(entityName));
700                        e.setMetaData(cme);
701                        throw e;
702                }
703                catch (HibernateException ex)
704                {
705                        throw new BaseException(ex);
706                }
707        }
708
709
710        /**
711         * Update an existing data object in the database.
712         */
713        static void updateData(Session session, BasicData data)
714                        throws BaseException
715        {
716                assert session != null : "session == null";
717                assert data != null : "data == null";
718                try
719                {
720                        session.update(data);
721                }
722                catch (HibernateException ex)
723                {
724                        throw new BaseException(ex);
725                }
726        }
727
728
729        /**
730         * Reconnect a disconnected data object to the session, discarding changes
731         * that have been made while the object was disconnected. This method is
732         * most useful with <code>lockOptions</code> = {@link LockOptions#NONE} if the
733         * logged in user only has read permission to the object.
734         * 
735         * @see DbControl#reattachItem(BasicItem)
736         */
737        static void lockData(Session session, BasicData data, LockOptions lockOptions)
738                        throws BaseException
739        {
740                assert session != null : "session == null";
741                assert data != null : "data == null";
742                assert lockOptions != null : "lockOptions == null";
743                try
744                {
745                        session.buildLockRequest(lockOptions).lock(data);
746                }
747                catch (HibernateException ex)
748                {
749                        throw new BaseException(ex);
750                }
751        }
752
753
754        /**
755         * Load a data item from the database when you know the id.
756         * 
757         * @param session The Hibernate session which is connected to the database
758         * @param clazz An object of this class is returned with the data
759         * @param id The id of the item to load
760         */
761        static <T> T loadData(Session session, Class<T> clazz, int id)
762                        throws BaseException
763        {
764                assert session != null : "session == null";
765                try
766                {
767                        return clazz.cast(session.get(clazz, id));
768                }
769                catch (HibernateException ex)
770                {
771                        throw new BaseException(ex);
772                }
773        }
774
775
776        /**
777         * Load a data item from the database using a statless session when you know
778         * the id.
779         * 
780         * @param session The Hibernate stateless session which is connected to the
781         *        database
782         * @param clazz An object of this class is returned with the data
783         * @param id The id of the item to load
784         */
785        static <T> T loadData(StatelessSession session, Class<T> clazz, int id)
786                        throws BaseException
787        {
788                assert session != null : "session == null";
789                try
790                {
791                        return clazz.cast(session.get(clazz, id));
792                }
793                catch (HibernateException ex)
794                {
795                        throw new BaseException(ex);
796                }
797        }
798
799
800        /**
801         * Load a data item from the database using a statless session when you know
802         * the id.
803         * 
804         * @param session The Hibernate stateless session which is connected to the
805         *        database
806         * @param clazz An object of this class is returned with the data
807         * @param entityName The entity name of the item
808         * @param id The id of the item to load
809         */
810        static <T> T loadData(StatelessSession session, Class<T> clazz,
811                        String entityName, int id)
812                        throws BaseException
813        {
814                assert session != null : "session == null";
815                try
816                {
817                        return clazz.cast(session.get(entityName, id));
818                }
819                catch (HibernateException ex)
820                {
821                        throw new BaseException(ex);
822                }
823        }
824
825
826        /**
827         * Load a data item from the database using a query. If the query returns
828         * more than one item, only the first is returned.
829         * 
830         * @param clazz An object of this class is returned with the data
831         * @param query A <code>Query</code> object which should select one item
832         *        of the specified class
833         */
834        static <T> T loadData(Class<T> clazz, Query query)
835                        throws BaseException
836        {
837                assert query != null : "query == null";
838                try
839                {
840                        List items = query.list();
841                        Object data = null;
842                        if (items.size() != 0)
843                        {
844                                data = items.get(0);
845                                // Check for return of Long value as Integer
846                                if (Integer.class.isAssignableFrom(clazz))
847                                {
848                                        if (Long.class.isAssignableFrom(data.getClass()))
849                                        {
850                                                Long dataLong = (Long) data;
851                                                Integer dataInteger = (Integer) dataLong.intValue();
852                                                return clazz.cast(dataInteger);
853                                        }
854                                }
855                                return clazz.cast(data);
856                        }
857                        else
858                        {
859                                return null;
860                        }
861                }
862                catch (HibernateException ex)
863                {
864                        throw new BaseException(ex);
865                }
866        }
867
868
869        /**
870         * Exceute an update or delete query.
871         * 
872         * @param query A <code>Query</code> object which is executed using the
873         *        {@link Query#executeUpdate()} method
874         * @return The number of affected items
875         */
876        static int executeUpdate(Query query)
877                        throws BaseException
878        {
879                assert query != null : "query == null";
880                try
881                {
882                        return query.executeUpdate();
883                }
884                catch (HibernateException ex)
885                {
886                        throw new BaseException(ex);
887                }
888        }
889
890
891        /**
892         * Get the value of a property on a data object using Hibernate metadata
893         * methods. This method is useful for generic export tools.
894         * 
895         * @see Metadata#getPropertyValue(BasicItem, String)
896         */
897        static Object getPropertyValue(BasicData data, String propertyName)
898                        throws BaseException
899        {
900                assert data != null : "data == null";
901                try
902                {
903                        ClassMetadata cmd = getClassMetadata(Hibernate.getClass(data)
904                                .getName());
905                        Object value = cmd.getPropertyValue(data, propertyName,
906                                EntityMode.POJO);
907                        return value;
908                }
909                catch (HibernateException ex)
910                {
911                        throw new BaseException(ex);
912                }
913        }
914
915
916        /**
917         * Initialise a collection on a data object. This method is useful if you
918         * plan to detach an item.
919         * 
920         * @see DbControl#detachItem(BasicItem)
921         */
922        static void initCollection(Session session, BasicData data,
923                        String collectionName)
924                        throws BaseException
925        {
926                assert session != null : "session == null";
927                assert data != null : "data == null";
928                try
929                {
930                        ClassMetadata cmd = getClassMetadata(Hibernate.getClass(data)
931                                .getName());
932                        Object collection = cmd.getPropertyValue(data, collectionName,
933                                EntityMode.POJO);
934                        Hibernate.initialize(collection);
935                }
936                catch (HibernateException ex)
937                {
938                        throw new BaseException(ex);
939                }
940        }
941
942
943        /**
944         * Delete a data item from the database.
945         */
946        static void deleteData(Session session, BasicData data)
947                        throws BaseException
948        {
949                assert session != null : "session == null";
950                assert data != null : "data == null";
951                try
952                {
953                        session.delete(data);
954                }
955                catch (HibernateException ex)
956                {
957                        throw new BaseException(ex);
958                }
959        }
960
961
962        /**
963         * Remove a data item from the internal cache of Hibernate. This also means
964         * that changes to the object will not be saved to the database unless it is
965         * reconnected again.
966         * 
967         * @see #updateData(Session, BasicData)
968         * @see #lockData(Session, BasicData, LockOptions)
969         */
970        static void evictData(Session session, BasicData data)
971                        throws BaseException
972        {
973                assert session != null : "session == null";
974                assert data != null : "data == null";
975                try
976                {
977                        session.evict(data);
978                }
979                catch (HibernateException ex)
980                {
981                        throw new BaseException(ex);
982                }
983        }
984
985
986        /**
987         * Create a Hibernate query.
988         */
989        static Query createQuery(Session session, String hql)
990                        throws BaseException
991        {
992                assert session != null : "session == null";
993                assert hql != null : "hql == null";
994                try
995                {
996                        return session.createQuery(hql);
997                }
998                catch (HibernateException ex)
999                {
1000                        throw new BaseException(ex);
1001                }
1002        }
1003
1004
1005        /**
1006         * Create a Hibernate query using a stateless session.
1007         */
1008        static Query createQuery(StatelessSession session, String hql)
1009                        throws BaseException
1010        {
1011                assert session != null : "session == null";
1012                assert hql != null : "hql == null";
1013                try
1014                {
1015                        return session.createQuery(hql);
1016                }
1017                catch (HibernateException ex)
1018                {
1019                        throw new BaseException(ex);
1020                }
1021        }
1022
1023
1024        /**
1025         * Create a Hibernate SQL query.
1026         */
1027        static SQLQuery createSqlQuery(Session session, String sql)
1028                        throws BaseException
1029        {
1030                assert session != null : "session == null";
1031                assert sql != null : "sql == null";
1032                try
1033                {
1034                        return session.createSQLQuery(sql);
1035                }
1036                catch (HibernateException ex)
1037                {
1038                        throw new BaseException(ex);
1039                }
1040        }
1041
1042
1043        /**
1044         * Create a Hibernate SQL query using a stateless session.
1045         */
1046        static SQLQuery createSqlQuery(StatelessSession session, String sql)
1047                        throws BaseException
1048        {
1049                assert session != null : "session == null";
1050                assert sql != null : "sql == null";
1051                try
1052                {
1053                        return session.createSQLQuery(sql);
1054                }
1055                catch (HibernateException ex)
1056                {
1057                        throw new BaseException(ex);
1058                }
1059        }
1060
1061
1062        /**
1063         * Enable the filter with the given name.
1064         */
1065        static Filter enableFilter(Session session, String name)
1066                        throws BaseException
1067        {
1068                assert session != null : "session == null";
1069                assert name != null : "name == null";
1070                try
1071                {
1072                        return session.enableFilter(name);
1073                }
1074                catch (HibernateException ex)
1075                {
1076                        throw new BaseException(ex);
1077                }
1078        }
1079
1080
1081        /**
1082         * Disable the filter with the given name.
1083         */
1084        static void disableFilter(Session session, String name)
1085                        throws BaseException
1086        {
1087                assert session != null : "session == null";
1088                assert name != null : "name == null";
1089                try
1090                {
1091                        session.disableFilter(name);
1092                }
1093                catch (HibernateException ex)
1094                {
1095                        throw new BaseException(ex);
1096                }
1097        }
1098
1099
1100        /**
1101         * Load a <code>List</code> of items from the database using a query.
1102         * 
1103         * @param clazz The list should contain objects of this class
1104         * @param query The query to execute
1105         */
1106        @SuppressWarnings(
1107                {
1108                        "unchecked"
1109                })
1110        static <T> List<T> loadList(Class<T> clazz, Query query)
1111                        throws BaseException
1112        {
1113                assert query != null : "query == null";
1114                try
1115                {
1116                        return query.list();
1117                }
1118                catch (Throwable ex)
1119                {
1120                        throw new BaseException(ex);
1121                }
1122        }
1123
1124
1125        /**
1126         * Scroll through the result of a query using an iterator.
1127         * 
1128         * @param clazz The iterator returns objects of this class
1129         * @param query The query to execute
1130         */
1131        static <T> ScrollIterator<T> loadIterator(Class<T> clazz, Query query)
1132                        throws BaseException
1133        {
1134                assert query != null : "query == null";
1135                try
1136                {
1137                        ScrollableResults result = query.scroll(ScrollMode.FORWARD_ONLY);
1138                        return new ScrollIterator<T>(clazz, result);
1139                }
1140                catch (Throwable ex)
1141                {
1142                        throw new BaseException(ex);
1143                }
1144        }
1145
1146
1147        /**
1148         * Get a predefined HQL query.
1149         * 
1150         * @see PredefinedQuery
1151         */
1152        static Query getPredefinedQuery(Session session, String name)
1153                        throws BaseException
1154        {
1155                assert session != null : "session == null";
1156                assert name != null : "name == null";
1157                return createQuery(session, PredefinedQuery.getQueryString(name));
1158        }
1159
1160
1161        /**
1162         * Get a predefined HQL query using the stateless session.
1163         * 
1164         * @see PredefinedQuery
1165         */
1166        static Query getPredefinedQuery(StatelessSession session, String name)
1167                        throws BaseException
1168        {
1169                assert session != null : "session == null";
1170                assert name != null : "name == null";
1171                return createQuery(session, PredefinedQuery.getQueryString(name));
1172        }
1173
1174
1175        /**
1176         * Get a predefined SQL query.
1177         * 
1178         * @see PredefinedQuery
1179         */
1180        static Query getPredefinedSQLQuery(Session session, String name)
1181                        throws BaseException
1182        {
1183                assert session != null : "session == null";
1184                assert name != null : "name == null";
1185                return createSqlQuery(session, PredefinedQuery.getQueryString(name));
1186        }
1187
1188
1189        /**
1190         * Get a predefined SQL query using the stateless session.
1191         * 
1192         * @see PredefinedQuery
1193         */
1194        static Query getPredefinedSQLQuery(StatelessSession session, String name)
1195                        throws BaseException
1196        {
1197                assert session != null : "session == null";
1198                assert name != null : "name == null";
1199                return createSqlQuery(session, PredefinedQuery.getQueryString(name));
1200        }
1201
1202
1203        @SuppressWarnings(
1204                {
1205                        "unchecked"
1206                })
1207        static Iterator<PersistentClass> getClassMappings()
1208        {
1209                assert cfg != null : "HibernateUtil has not been initialised";
1210                return cfg.getClassMappings();
1211        }
1212
1213
1214        static PersistentClass getClassMapping(String entityName)
1215        {
1216                assert cfg != null : "HibernateUtil has not been initialised";
1217                return cfg.getClassMapping(entityName);
1218        }
1219
1220
1221        static ClassMetadata getClassMetadata(String entityName)
1222        {
1223                assert sf != null : "HibernateUtil has not been initialised";
1224                return sf.getClassMetadata(entityName);
1225        }
1226
1227
1228        static Mapping getMapping()
1229        {
1230                return (Mapping) sf;
1231        }
1232
1233
1234        /**
1235         * Get the database dialect.
1236         */
1237        public static Dialect getDialect()
1238        {
1239                return dialect;
1240        }
1241
1242
1243        /**
1244         * Check if theta joins or ANSI joins are used by the database.
1245         */
1246        public static boolean useThetaJoin(QueryType queryType)
1247        {
1248                return useThetaJoin && queryType == QueryType.SQL;
1249        }
1250
1251
1252        /**
1253         * Quote a string with the default quote type for the current database
1254         * engine. Ie. with MySQL: <code>value --&gt; `value`</code>, and with
1255         * any ANSI-compatible database: <code>value --&gt; "value"</code>. This
1256         * method uses the <code>Dialect.openQuote</code> and
1257         * <code>Dialect.closeQuote</code> methods of the currently installed
1258         * Hibernate dialect.
1259         * 
1260         * @param value The value to quote
1261         * @return The quoted value
1262         * @see #getDialect()
1263         */
1264        public static String quote(String value)
1265        {
1266                if (value != null)
1267                        value = dialect.openQuote() + value + dialect.closeQuote();
1268                return value;
1269        }
1270
1271
1272        /**
1273         * Removes the package name part of a class name and any ending 'Data'.
1274         */
1275        static String getShortEntityName(String entityName)
1276        {
1277                int lastDot = entityName.lastIndexOf('.');
1278                if (lastDot >= 0)
1279                {
1280                        entityName = entityName.substring(lastDot + 1);
1281                }
1282                entityName = entityName.replaceAll("Data$", "");
1283                return entityName;
1284        }
1285
1286
1287        static void testTransactions()
1288                        throws BaseException
1289        {
1290                NewsData news = new NewsData();
1291                news.setName("Transaction failure");
1292                news
1293                        .setDescription("If you see this new item it is because your database doesn't support transactions. " + "We don't recommend running Proteios without transactions.");
1294                news.setStartDate(new Date());
1295                news.setNewsDate(new Date());
1296                org.hibernate.Session session = null;
1297                org.hibernate.Transaction tx = null;
1298                boolean ok = false;
1299                try
1300                {
1301                        // Insert news item, but rollback the transaction
1302                        session = HibernateUtil.newSession();
1303                        tx = HibernateUtil.newTransaction(session);
1304                        HibernateUtil.saveData(session, news);
1305                        int newsId = news.getId();
1306                        HibernateUtil.rollback(tx);
1307                        HibernateUtil.close(session);
1308                        // Evict the item from the cache and, in another session, try to
1309                        // load the news item
1310                        sf.getCache().containsEntity(NewsData.class, newsId);
1311                        session = HibernateUtil.newSession();
1312                        tx = HibernateUtil.newTransaction(session);
1313                        news = HibernateUtil.loadData(session, NewsData.class, newsId);
1314                        ok = news == null;
1315                }
1316                finally
1317                {
1318                        if (tx != null)
1319                                HibernateUtil.rollback(tx);
1320                        if (session != null)
1321                                HibernateUtil.close(session);
1322                }
1323                if (!ok)
1324                        throw new BaseException(
1325                                "Your database doesn't support transactions.");
1326        }
1327}