1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
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);
120
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
135
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
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
195
196 protected EntityManager getEntityManager() {
197
198 return this.entityManager.get();
199
200 }
201
202
203 @Override
204 protected void onMakePartition() {
205
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'");
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");
269 results.addAll(query.getResultList());
270
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
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
321
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
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 {
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
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
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
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462 } catch (Exception e) {
463
464 log.warn(entity + " (maybe) already persisted - trying update instead...", e);
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
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
552
553
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
580
581
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
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
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
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
755 }
756 }
757
758
759
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 }