View Javadoc

1   /*
2    * Copyright 2006-2010 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package net.sourceforge.domian.repository;
17  
18  
19  import java.io.File;
20  import java.util.Collection;
21  import java.util.HashSet;
22  import java.util.Iterator;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.NoSuchElementException;
26  import java.util.concurrent.Callable;
27  
28  import javax.persistence.EntityManager;
29  import javax.persistence.EntityManagerFactory;
30  import javax.persistence.EntityTransaction;
31  import javax.persistence.NoResultException;
32  import javax.persistence.Query;
33  
34  import org.apache.commons.lang.NotImplementedException;
35  import org.apache.commons.lang.Validate;
36  import org.hibernate.CacheMode;
37  import org.hibernate.FlushMode;
38  import org.hibernate.Session;
39  
40  import net.sourceforge.domian.entity.Entity;
41  import net.sourceforge.domian.specification.CompositeSpecification;
42  import net.sourceforge.domian.specification.JpqlQueryHolder;
43  import net.sourceforge.domian.specification.Specification;
44  
45  import static java.lang.Boolean.FALSE;
46  import static java.lang.Boolean.TRUE;
47  import static javax.persistence.Persistence.createEntityManagerFactory;
48  import static net.sourceforge.domian.repository.PersistenceDefinition.DELEGATED;
49  import static net.sourceforge.domian.specification.Specification2HqlConverter.convertSpecification2HqlQuery;
50  import static net.sourceforge.domian.specification.Specification2JpqlConverter.convert2DeleteStatement;
51  import static net.sourceforge.domian.specification.Specification2JpqlConverter.convertJpaMappedSpecificationType2PreparedJpqlQuery;
52  import static net.sourceforge.domian.specification.SpecificationUtils.updateEntityState;
53  import static net.sourceforge.domian.util.InstrumentationUtils.buildThreadNumberAndMessage;
54  import static org.apache.commons.lang.SystemUtils.FILE_SEPARATOR;
55  
56  
57  /**
58   * This repository implementation is based on the <a href="http://www.hibernate.org">Hibernate</a> object-relational mapping (ORM) tool,
59   * and its implementation of the Java Persistence API 1.0 (JPA).
60   * The Hibernate Query Language (HQL) is used as a fallback solution for both JPA EntityManager and JPA Query Language (JPQL) where suitable.
61   * <p/>
62   * <i>
63   * Highly experimental code as it has yet to be in any form of serious use.
64   * The only tested RDBMS is <a href="http://www.h2database.com/html/main.html">H2</a>.
65   * </i>
66   * <p/>
67   * <i>
68   * Conversion of specification object graph to JPQL/HQL is not yet completed!
69   * </i>
70   * <p/>
71   * <i>
72   * Partitioning of this repository is not yet completed!
73   * </i>
74   * <p/>
75   * This repository has a table naming convention: all table names must end <code>_TABLE</code>.
76   * In your JPA ORM configuration file, you must explicitely configure all table names ending with <code>_TABLE</code>.
77   * JPA does not seem to allow custom naming strategies...
78   * <p/>
79   * The persistence definition supported by this repository is {@code PersistenceDefinition.DELEGATED}.
80   *
81   * @author Eirik Torske
82   * @see <a href="http://en.wikipedia.org/wiki/Java_Persistence_API">Java Persistence API (JPA)</a>
83   * @since 0.4.2
84   */
85  public class HibernateRepository<T extends Entity> extends AbstractDomianCoreRepository<T> implements BinaryFormatRepository<T> {
86  
87      protected Rdbms rdbms;
88  
89      protected String rdbmsServerName;
90      protected String repositoryRootDirectoryString;
91      protected String repositoryId;
92  
93      protected Map<String, String> configurationMap;
94  
95      protected String repositoryDirectoryString;
96      protected File repositoryDirectory;
97  
98      protected EntityManagerFactory entityManagerFactory;
99  
100     protected List<String> tableNameList;
101 
102 
103     public HibernateRepository(final Rdbms rdbms, final String repositoryId) {
104         this(rdbms,
105              "localhost",
106              rdbms.getDefaultRepositoryRootDirectoryString(DEFAULT_DOMIAN_ROOT_PATH),
107              repositoryId);
108     }
109 
110 
111     public HibernateRepository(final Rdbms rdbms,
112                                final String rdbmsServerName,
113                                final String repositoryRootDirectoryString,
114                                final String repositoryId) {
115         this(rdbms,
116              rdbmsServerName,
117              repositoryRootDirectoryString,
118              repositoryId,
119              null);   // No custom configuration properties
120         //null); // No need for a synchronizer - using inherent RDBMS optimistic locking support
121     }
122 
123 
124     public HibernateRepository(final Rdbms rdbms,
125                                final String rdbmsServerName,
126                                final String repositoryRootDirectoryString,
127                                final String repositoryId,
128                                final Map<String, String> configurationMap) {
129 
130         Validate.notNull(rdbms, "The RDBMS parameter cannot be empty");
131         Validate.notEmpty(rdbmsServerName, "The RDBMS server name parameter cannot be empty");
132         Validate.notEmpty(repositoryRootDirectoryString, "The repository root path parameter cannot be empty");
133         Validate.notEmpty(repositoryId, "The repository ID parameter cannot be empty");
134         //Validate.notNull(configurationMap, "The repository configuration map parameter cannot be empty");
135         //Validate.notNull(synchronizer, "The synchronizer parameter cannot be null");
136 
137         this.rdbms = rdbms;
138         this.rdbmsServerName = rdbmsServerName;
139         this.repositoryRootDirectoryString = repositoryRootDirectoryString;
140         this.repositoryId = repositoryId;
141         this.configurationMap = configurationMap;
142         this.repositoryDirectoryString = this.repositoryRootDirectoryString + FILE_SEPARATOR + this.repositoryId;
143 
144         if (!this.rdbms.isSupported()) {
145             log.warn("RDBMS " + this.rdbms.getName() + " is not supported/tested");
146         }
147         try {
148             this.entityManagerFactory = createEntityManagerFactory(getJpaPersistenceUnitName(),
149                                                                    this.rdbms.getConfiguration(this.rdbmsServerName,
150                                                                                                this.repositoryDirectoryString,
151                                                                                                this.repositoryId,
152                                                                                                this.configurationMap));
153         } catch (Exception e) {
154             log.error("Unable to create EntityManagerFactory", e.getCause());
155         }
156         Validate.notNull(this.entityManagerFactory, "EntityManagerFactory is null - really no point to continue...");
157 
158         //this.entityManager = createEntityManager();
159 
160         this.usesNativePartitioningSupport = TRUE;
161         this.supportsRecursiveIndexing = TRUE;
162 
163         this.tableNameList = getTableNames();
164     }
165 
166 
167     private EntityManager createEntityManager() {
168         final EntityManager entityManager = HibernateRepository.this.entityManagerFactory.createEntityManager();
169         final Session hibernateSession = (Session) entityManager.getDelegate();
170         hibernateSession.setFlushMode(FlushMode.ALWAYS);
171         hibernateSession.setCacheMode(CacheMode.IGNORE);
172         log.debug(buildThreadNumberAndMessage("Hibernate session/JPA EntityManager created [" +
173                                               "hashCode=" + entityManager.hashCode() + ", " +
174                                               "flushMode=" + hibernateSession.getFlushMode() + ", " +
175                                               "cacheMode=" + hibernateSession.getCacheMode() + ", " +
176                                               "entityMode=" + hibernateSession.getEntityMode() +
177                                               "]"));
178         return entityManager;
179     }
180 
181 
182     public static String getJpaPersistenceUnitName() {
183         return "domian-hibernate-repository";
184     }
185 
186     protected final ThreadLocal<EntityManager> entityManager =
187             new ThreadLocal<EntityManager>() {
188                 @Override
189                 protected EntityManager initialValue() {
190                     return createEntityManager();
191                 }
192             };
193 
194     //private EntityManager entityManager;
195 
196     protected EntityManager getEntityManager() {
197         //return this.entityManagerFactory.createEntityManager(); // Fresh entity manager for each invocation...
198         return this.entityManager.get(); // Thread local
199         //return this.entityManager;
200     }
201 
202 
203     @Override
204     protected void onMakePartition() {
205         // TODO: partition repository cannot (maybe) have any cascading/recursive indexing stuff...
206         log.warn("Partitioning is not yet supported");
207     }
208 
209 
210     List<String> getTableNames() {
211         final EntityManager entityManager = getEntityManager();
212         EntityTransaction tx = null;
213         List<String> results = null;
214         try {
215             tx = entityManager.getTransaction();
216             if (!tx.isActive()) {
217                 tx.begin();
218             }
219             Query query = null;
220             switch (this.rdbms) {
221                 case H2:
222                     query = entityManager.createNativeQuery("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'TABLE'"); // SQL
223                     break;
224                 default:
225                     log.warn("Unable to obtain schema table names for RDBMS " + this.rdbms.getName());
226             }
227             if (query != null) {
228                 results = query.getResultList();
229             }
230             tx.commit();
231 
232         } catch (Exception e) {
233             log.error("Unable to obtain schema table names", e);
234             if (tx != null && tx.isActive()) {
235                 tx.rollback();
236             }
237         }
238         return results;
239     }
240 
241 
242     @Override
243     protected Boolean contains(final T entity) {
244         return getEntityManager().contains(entity);
245     }
246 
247 
248     @Override
249     public <V extends T> Iterator<V> iterateAllEntitiesSpecifiedBy(final Specification<V> specification) {
250         Validate.notNull(specification, "Specification parameter cannot be null");
251         return new HibernateRepositoryIterator<V>(specification);
252     }
253 
254 
255     @Override
256     public <V extends T> Collection<V> findAllEntitiesSpecifiedBy(final Specification<V> specification) {
257         Validate.notNull(specification, "Specification parameter cannot be null");
258         final EntityManager entityManager = getEntityManager();
259         EntityTransaction tx = null;
260         Collection<V> results = new HashSet<V>();
261 
262         if (specification.getType().equals(Entity.class)) {
263             try {
264                 tx = entityManager.getTransaction();
265                 if (!tx.isActive()) {
266                     tx.begin();
267                 }
268                 final Query query = entityManager.createQuery("from java.lang.Object o"); // HQL
269                 results.addAll(query.getResultList());
270                 //if (results != null) {
271                 for (final V foundEntity : results) {
272                     entityManager.refresh(foundEntity);
273                 }
274                 //}
275                 tx.commit();
276 
277             } catch (Exception e) {
278                 log.error("Unable to perform find operation with specification=" + specification, e);
279                 if (tx != null && tx.isActive()) {
280                     tx.rollback();
281                 }
282             }
283 
284         } else if (specification instanceof CompositeSpecification) {
285             final JpqlQueryHolder jpqlQueryHolder =
286                     convertJpaMappedSpecificationType2PreparedJpqlQuery((CompositeSpecification) specification, this.tableNameList);
287             try {
288                 tx = entityManager.getTransaction();
289                 if (!tx.isActive()) {
290                     tx.begin();
291                 }
292                 final Query query = entityManager.createQuery(jpqlQueryHolder.getJpqlQueryString());
293                 for (String parameterName : jpqlQueryHolder.getParameterMap().keySet()) {
294                     query.setParameter(parameterName, jpqlQueryHolder.getParameterMap().get(parameterName));
295                 }
296                 results.addAll(query.getResultList());
297                 //if (results != null) {
298                 for (final V foundEntity : results) {
299                     entityManager.refresh(foundEntity);
300                 }
301                 //}
302                 tx.commit();
303 
304             } catch (Exception e) {
305                 if (tx != null && tx.isActive()) {
306                     tx.rollback();
307                 }
308                 log.error("Unable to perform find operation with specification=" + specification, e);
309             }
310 
311         } else if (specification instanceof PartitionRepositoryInvocationHandler.PartitionAwareSpecification) {
312             PartitionRepositoryInvocationHandler.PartitionAwareSpecification partitionAwareSpecification = (PartitionRepositoryInvocationHandler.PartitionAwareSpecification) specification;
313             Specification<V> spec = partitionAwareSpecification.partitionAwareSpecification;
314             return findAllEntitiesSpecifiedBy(spec);
315 
316         } else {
317             throw new NotImplementedException("specifification=" + specification);
318         }
319         /*
320         if (results != null && results.isEmpty()) {
321             return emptySet();
322         }
323         */
324         return results;
325     }
326 
327 
328     @Override
329     public <V extends T> V findSingleEntitySpecifiedBy(final Specification<V> specification) {
330         Validate.notNull(specification, "Specification parameter cannot be null");
331         final EntityManager entityManager = getEntityManager();
332         EntityTransaction tx = null;
333         V result = null;
334 
335         if (specification.getType().equals(Entity.class)) {
336             // Use Hibernate HQL polymorphy support for Entity type specifications
337             final String hqlExpression = convertSpecification2HqlQuery(specification);
338             try {
339                 tx = entityManager.getTransaction();
340                 if (!tx.isActive()) {
341                     tx.begin();
342                 }
343                 final List<V> results = entityManager.createQuery(hqlExpression).getResultList();
344                 if (results != null) {
345                     for (final V foundEntity : results) {
346                         entityManager.refresh(foundEntity);
347                     }
348                 }
349                 tx.commit();
350                 if (results.size() == 1) {
351                     result = results.iterator().next();
352 
353                 } else if (results.size() < 1) {
354                     return null;
355 
356                 } else if (results.size() > 1) {
357                     int numberOfApprovedEntities = 0;
358                     V currentApprovedEntity = null;
359                     for (final V entity : results) {
360                         if (specification.isSatisfiedBy(entity)) {
361                             ++numberOfApprovedEntities;
362                             currentApprovedEntity = entity;
363                         }
364                     }
365                     if (numberOfApprovedEntities == 1) {
366                         result = currentApprovedEntity;
367                     } else {
368                         throw new IllegalArgumentException("More than one entity were found when only one was expected");
369                     }
370                 }
371 
372             } catch (Exception e) {
373                 if (tx != null && tx.isActive()) {
374                     tx.rollback();
375                 }
376                 log.error("Unable to perform find-single operation with specification=" + specification, e);
377             }
378 
379         } else { // Convert Specification into JPQL
380 
381             if (specification instanceof CompositeSpecification) {
382                 final JpqlQueryHolder jpqlQueryHolder = convertJpaMappedSpecificationType2PreparedJpqlQuery((CompositeSpecification) specification, this.tableNameList);
383                 try {
384                     tx = entityManager.getTransaction();
385                     if (!tx.isActive()) {
386                         tx.begin();
387                     }
388                     final Query query = entityManager.createQuery(jpqlQueryHolder.getJpqlQueryString());
389                     for (String parameterName : jpqlQueryHolder.getParameterMap().keySet()) {
390                         query.setParameter(parameterName, jpqlQueryHolder.getParameterMap().get(parameterName));
391                     }
392                     try {
393                         result = (V) query.getSingleResult();
394 
395                     } catch (NoResultException e) {
396                         // Deliberately left empty
397 
398                     } catch (Exception e) {
399                         System.err.println("getSingleResult(); exception caught: " + e);
400                         e.printStackTrace(System.err);
401                     }
402                     if (result != null) {
403                         entityManager.refresh(result);
404                     }
405                     tx.commit();
406 
407                 } catch (Exception e) {
408                     if (tx != null && tx.isActive()) {
409                         tx.rollback();
410                     }
411                     log.error("Unable to perform find-single operation with specification=" + specification, e);
412                 }
413 
414             } else {
415                 throw new NotImplementedException();
416             }
417         }
418         return result;
419     }
420 
421 
422     @Override
423     public <V extends T> void put(final V entity) {
424         if (entity == null) {
425             log.debug("Skipping putting of null entity");
426 
427         } else {
428             final EntityManager entityManager = getEntityManager();
429             //System.out.println("put() entityManager=" + entityManager);
430             EntityTransaction tx = null;
431             try {
432                 tx = entityManager.getTransaction();
433                 if (!tx.isActive()) {
434                     tx.begin();
435                 }
436                 entityManager.persist(entity);
437                 log.debug(entity + " persisted (not yet comitted)");
438                 tx.commit();
439                 //entityManager.clear();
440 
441                 /*
442             } catch (PersistenceException e) {
443                 e.printStackTrace(System.err);
444                 if (e.getCause() instanceof HibernateException) {
445                     if (e.getCause().getMessage().equals("Illegal attempt to associate a collection with two open sessions")) {
446                         tx.rollback();
447                         tx = entityManager.getTransaction();
448                         try {
449                             if (!tx.isActive()) {
450                                 tx.begin();
451                             }
452                             //entityManager.clear();
453                             //log.debug(entity + " updated");
454                             //put(entity);
455                             tx.commit();
456                         } catch (Exception e2) {
457                             log.error("Neither able to store nor update entity=" + entity, e2);
458                         }
459                     }
460                 }
461                   */
462             } catch (Exception e) {
463                 //log.warn(entity + " (maybe) already persisted - trying update instead...");
464                 log.warn(entity + " (maybe) already persisted - trying update instead...", e); // For testing - too noisy
465                 if (tx != null && tx.isActive()) {
466                     tx.rollback();
467                     tx = entityManager.getTransaction();
468                     try {
469                         if (!tx.isActive()) {
470                             tx.begin();
471                         }
472                         entityManager.merge(entity);
473                         log.debug(entity + " updated (not yet comitted)");
474                         tx.commit();
475                     } catch (Exception e2) {
476                         log.error("Neither able to store nor update entity=" + entity, e2);
477                     }
478 
479                 } else {
480                     update(entity);
481                 }
482             }
483         }
484     }
485 
486 
487     protected class Update implements Callable<Void> {
488         private Entity entity;
489         protected Update(Entity entity) {
490             this.entity = entity;
491         }
492 
493         @Override
494         public Void call() throws Exception {
495             final EntityManager entityManager = getEntityManager();
496             EntityTransaction tx = null;
497             try {
498                 tx = entityManager.getTransaction();
499                 if (!tx.isActive()) {
500                     tx.begin();
501                 }
502                 entityManager.merge(entity);
503                 //entityManager.flush();
504                 tx.commit();
505                 return null;
506 
507             } catch (Exception e) {
508                 if (tx != null && tx.isActive()) {
509                     tx.rollback();
510                 }
511                 throw e;
512             }
513         }
514     }
515 
516     @Override
517     public <V extends T> void update(final V entity) {
518         update(entity, null);
519     }
520 
521     @Override
522     public <V extends T> void update(final V entity, Specification<?> deltaSpecification) {
523         if (entity == null) {
524             log.debug("Skipping putting of null entity");
525 
526         } else {
527             final int RETRIES = 3;
528             int index = 0;
529             boolean success = false;
530             Callable<Void> update = new Update(entity);
531             while (index < RETRIES && !success) {
532                 try {
533                     update.call();
534                     success = true;
535 
536                 } catch (Exception e) {
537                     log.warn("Exception caught while updating entity [type=" + entity.getClass().getName() + ", entityId=" + entity.getEntityId() + "]");
538                     ++index;
539                     final EntityManager entityManager = getEntityManager();
540                     entityManager.clear();
541                     final V reAttachedEntity = (V) entityManager.find(entity.getClass(), entity.getEntityId());
542                     updateEntityState(reAttachedEntity, deltaSpecification);
543                     update = new Update(reAttachedEntity);
544                 }
545             }
546         }
547     }
548 
549 
550     /**
551      * <i>Extra method</i>
552      * <p/>
553      * Purges all records in givan table without no further ado.
554      */
555     public void removeAllRecordsInTable(final String tableName) {
556         long numberOfEntitiesRemoved = 0;
557         final EntityManager entityManager = getEntityManager();
558         EntityTransaction tx = null;
559         try {
560             tx = entityManager.getTransaction();
561             if (!tx.isActive()) {
562                 tx.begin();
563             }
564             Query query = entityManager.createNativeQuery("delete from " + tableName);
565             numberOfEntitiesRemoved = query.executeUpdate();
566             tx.commit();
567 
568         } catch (Exception e) {
569             log.error("Removal of entities failed", e);
570             if (tx != null && tx.isActive()) {
571                 tx.rollback();
572             }
573         }
574         log.info(numberOfEntitiesRemoved + " records explicitely deleted in table " + tableName.toUpperCase());
575     }
576 
577 
578     /**
579      * <i>Extra method</i>
580      * <p/>
581      * Purges all entities in repository without no further ado.
582      */
583     public Long removeAllEntities() {
584         long numberOfEntitiesRemoved = 0;
585         final EntityManager entityManager = getEntityManager();
586         EntityTransaction tx = null;
587         try {
588             synchronized (this) {
589                 tx = entityManager.getTransaction();
590                 if (!tx.isActive()) {
591                     tx.begin();
592                 }
593             }
594             final Query query = entityManager.createQuery("delete from java.lang.Object");
595             numberOfEntitiesRemoved = query.executeUpdate();
596             tx.commit();
597 
598         } catch (Exception e) {
599             if (tx != null && tx.isActive()) {
600                 tx.rollback();
601             }
602             log.error("Removal of entities failed", e);
603         }
604         log.debug(numberOfEntitiesRemoved + " entities removed");
605         return numberOfEntitiesRemoved;
606     }
607 
608 
609     @Override
610     public <V extends T> Long removeAllEntitiesSpecifiedBy(final Specification<V> specification) {
611         Validate.notNull(specification, "Specification parameter cannot be null");
612         if (specification.getType().equals(Entity.class)) {
613             return removeAllEntities();
614         }
615         long numberOfRemovedEntitites = 0;
616         final EntityManager entityManager = getEntityManager();
617         EntityTransaction tx = null;
618         try {
619             tx = entityManager.getTransaction();
620             if (!tx.isActive()) {
621                 tx.begin();
622             }
623             final List<String> jpqlQueryStringList = convert2DeleteStatement(specification, this.tableNameList);
624             for (String jpqlQueryString : jpqlQueryStringList) {
625                 log.debug("JPQL query=" + jpqlQueryString);
626                 if (jpqlQueryString == null) {
627                     throw new RepositoryException("JPQL query cannot be null [converted from specification=" + specification + "]");
628                 }
629                 final Query query = entityManager.createQuery(jpqlQueryString);
630                 numberOfRemovedEntitites += query.executeUpdate();
631             }
632             tx.commit();
633 
634         } catch (Exception e) {
635             if (tx != null && tx.isActive()) {
636                 tx.rollback();
637             }
638             log.error("Unable to perform remove operation with specification=" + specification, e);
639         }
640         return numberOfRemovedEntitites;
641     }
642 
643 
644     @Override
645     public <V extends T> Boolean remove(final V entity) {
646         if (entity == null) {
647             return FALSE;
648         }
649         Boolean success = TRUE;
650         final EntityManager entityManager = getEntityManager();
651         EntityTransaction tx = null;
652         try {
653             tx = entityManager.getTransaction();
654             if (!tx.isActive()) {
655                 tx.begin();
656             }
657             final V attachedEntity = (V) entityManager.find(entity.getClass(), entity.getEntityId());
658             if (attachedEntity == null) {
659                 tx.commit();
660                 return FALSE;
661             }
662             if (!entityManager.contains(attachedEntity)) {
663                 tx.commit();
664                 return FALSE;
665             }
666             entityManager.remove(attachedEntity);
667             log.debug(entity + " removed");
668             tx.commit();
669 
670         } catch (Exception e) {
671             try {
672                 log.warn(entity + " NOT removed", e);
673             } catch (Exception e1) {
674                 // Deliberately left empty
675             }
676 
677             if (tx != null && tx.isActive()) {
678                 tx.rollback();
679             }
680             success = FALSE;
681         }
682         return success;
683     }
684 
685 
686     @Override
687     public File getRepositoryDirectory() {
688         return new File(this.repositoryDirectoryString);
689     }
690 
691 
692     @Override
693     public String getRepositoryId() {
694         return this.repositoryId;
695     }
696 
697 
698     @Override
699     public PersistenceDefinition getPersistenceDefinition() {
700         return DELEGATED;
701     }
702 
703 
704     @Override
705     public String getFormat() {
706         throw new NotImplementedException();
707     }
708 
709 
710     @Override
711     public void load() {
712         log.warn("load() is only applicable for repositories with asynchronous persistence [PersistenceDefinition.INMEMORY_AND_*]");
713     }
714 
715 
716     @Override
717     public void persist() {
718         log.info("persist() invoked - flushing and clearing/deattaching all entities in JPA entity manager");
719         EntityTransaction tx = null;
720         try {
721             final EntityManager entityManager = getEntityManager();
722             tx = entityManager.getTransaction();
723             if (!tx.isActive()) {
724                 tx.begin();
725             }
726             //entityManager.flush(); // Not necessary with FlushMode.ALWAYS
727             entityManager.clear();
728             tx.commit();
729 
730         } catch (Exception e) {
731             if (tx != null && tx.isActive()) {
732                 tx.rollback();
733             }
734             log.error("Unable to flush entities", e);
735         }
736     }
737 
738 
739     @Override
740     public EntityPersistenceMetaData getMetaDataFor(final T entity) {
741         throw new NotImplementedException();
742     }
743 
744 
745     @Override
746     public void close() {
747         //persist();
748         log.info("Permanently closing entity manager instance and entity manager factory instance");
749         if (getEntityManager().isOpen()) {
750             getEntityManager().close();
751         }
752         if (this.entityManagerFactory.isOpen()) {
753             this.entityManagerFactory.close();
754             //this.entityManagerFactory = null;
755         }
756     }
757 
758 
759     /** Custom iterator class for {@link HibernateRepository}. */
760     protected class HibernateRepositoryIterator<V extends T> implements Iterator<V> {
761 
762         protected final Specification<V> iteratorSpecification;
763         protected final Iterator<V> iterator;
764 
765         protected V currentEntity;
766         protected boolean nextIsInvoked = false;
767 
768         public HibernateRepositoryIterator(final Specification<V> specification) {
769             this.iteratorSpecification = specification;
770             final Collection<V> entityCollection = HibernateRepository.this.findAllEntitiesSpecifiedBy(this.iteratorSpecification);
771             this.iterator = entityCollection.iterator();
772         }
773 
774         @Override
775         public boolean hasNext() {
776             return this.iterator.hasNext();
777         }
778 
779         @Override
780         public V next() {
781             if (!this.iterator.hasNext()) {
782                 throw new NoSuchElementException();
783             }
784             this.currentEntity = this.iterator.next();
785             this.nextIsInvoked = true;
786             return this.currentEntity;
787         }
788 
789         @Override
790         public void remove() {
791             if (!this.nextIsInvoked) {
792                 throw new IllegalStateException();
793             }
794             this.iterator.remove();
795             HibernateRepository.this.remove(this.currentEntity);
796             nextIsInvoked = false;
797         }
798     }
799 }