source: VirtualCollectionRegistry/trunk/VirtualCollectionRegistry/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/VirtualCollectionRegistry.java @ 5441

Last change on this file since 5441 was 5441, checked in by Twan Goosen, 10 years ago

Reduced level of some logging statements from debug to trace

  • Property svn:eol-style set to native
File size: 25.3 KB
Line 
1package eu.clarin.cmdi.virtualcollectionregistry;
2
3import eu.clarin.cmdi.oai.provider.impl.OAIProvider;
4import eu.clarin.cmdi.virtualcollectionregistry.model.User;
5import eu.clarin.cmdi.virtualcollectionregistry.model.VirtualCollection;
6import eu.clarin.cmdi.virtualcollectionregistry.model.VirtualCollectionList;
7import eu.clarin.cmdi.virtualcollectionregistry.pid.PersistentIdentifier;
8import eu.clarin.cmdi.virtualcollectionregistry.pid.PersistentIdentifierProvider;
9import eu.clarin.cmdi.virtualcollectionregistry.query.ParsedQuery;
10import eu.clarin.cmdi.virtualcollectionregistry.service.VirtualCollectionValidator;
11import eu.clarin.cmdi.virtualcollectionregistry.service.VirtualCollectionValidatorFactory;
12import java.security.Principal;
13import java.util.Date;
14import java.util.List;
15import java.util.Timer;
16import java.util.TimerTask;
17import java.util.concurrent.atomic.AtomicBoolean;
18import javax.persistence.EntityManager;
19import javax.persistence.EntityTransaction;
20import javax.persistence.LockModeType;
21import javax.persistence.NoResultException;
22import javax.persistence.TypedQuery;
23import javax.persistence.criteria.CriteriaBuilder;
24import javax.persistence.criteria.CriteriaQuery;
25import javax.persistence.criteria.Order;
26import javax.persistence.criteria.Predicate;
27import javax.persistence.criteria.Root;
28import org.slf4j.Logger;
29import org.slf4j.LoggerFactory;
30import org.springframework.beans.factory.DisposableBean;
31import org.springframework.beans.factory.InitializingBean;
32import org.springframework.beans.factory.annotation.Autowired;
33import org.springframework.stereotype.Service;
34
35@Service
36public class VirtualCollectionRegistry implements InitializingBean, DisposableBean {
37
38    @Autowired
39    private DataStore datastore; //TODO: replace with Spring managed EM
40    @Autowired
41    private PersistentIdentifierProvider pid_provider;
42    @Autowired
43    private OAIProvider oaiProvider;
44    @Autowired
45    private VirtualCollectionValidatorFactory validatorFactory;
46
47    private static final Logger logger
48            = LoggerFactory.getLogger(VirtualCollectionRegistry.class);
49    private final AtomicBoolean intialized = new AtomicBoolean(false);
50    private final Timer timer
51            = new Timer("VirtualCollectionRegistry-Maintenance", true);
52
53    @Override
54    public void afterPropertiesSet() throws VirtualCollectionRegistryException {
55        // called by Spring directly after Bean construction
56        doInitalize();
57    }
58
59    private void doInitalize() throws VirtualCollectionRegistryException {
60        if (intialized.get()) {
61            throw new VirtualCollectionRegistryException("already initialized");
62        }
63        logger.info("Initializing virtual collection registry ...");
64        try {
65            // setup VCR maintenance task
66            timer.schedule(new TimerTask() {
67                @Override
68                public void run() {
69                    maintenance(this.scheduledExecutionTime());
70                }
71            }, 60000, 60000);
72            this.intialized.set(true);
73            logger.info("virtual collection registry successfully intialized");
74        } catch (RuntimeException e) {
75            logger.error("error initalizing virtual collection registry", e);
76            throw e;
77        }
78    }
79
80    @Override
81    public void destroy() throws VirtualCollectionRegistryException {
82        logger.info("Stopping Virtual Collection Registry maintenance schedule");
83        timer.cancel();
84
85        logger.info("Shutting down OAI provider");
86        oaiProvider.shutdown();
87    }
88
89    public long createVirtualCollection(Principal principal,
90            VirtualCollection vc) throws VirtualCollectionRegistryException {
91        if (principal == null) {
92            throw new NullPointerException("principal == null");
93        }
94        if (vc == null) {
95            throw new NullPointerException("vc == null");
96        }
97
98        logger.debug("creating virtual collection");
99
100        VirtualCollectionValidator validator = validatorFactory.createValidator();
101        validator.validate(vc);
102        try {
103            EntityManager em = datastore.getEntityManager();
104            em.getTransaction().begin();
105
106            // fetch user, if user does not exist create new
107            User user = fetchUser(em, principal);
108            if (user == null) {
109                user = new User(principal.getName());
110                em.persist(user);
111            }
112
113            // store virtual collection
114            vc.setOwner(user);
115            logger.debug("persisting new virtual collection", vc.getId());
116            em.persist(vc);
117            em.getTransaction().commit();
118            logger.debug("virtual collection created (id={})", vc.getId());
119            return vc.getId();
120        } catch (Exception e) {
121            logger.error("error while creating virtual collection", e);
122            throw new VirtualCollectionRegistryException(
123                    "error while creating virtual collection", e);
124        }
125    }
126
127    public long updateVirtualCollection(Principal principal, long id,
128            VirtualCollection vc) throws VirtualCollectionRegistryException {
129        if (principal == null) {
130            throw new NullPointerException("principal == null");
131        }
132        if (id <= 0) {
133            throw new IllegalArgumentException("id <= 0");
134        }
135        if (vc == null) {
136            throw new NullPointerException("vc == null");
137        }
138
139        logger.debug("updating virtual collection (id={})", id);
140
141        VirtualCollectionValidator validator = validatorFactory.createValidator();
142        validator.validate(vc);
143
144        try {
145            EntityManager em = datastore.getEntityManager();
146            em.getTransaction().begin();
147            VirtualCollection c = em.find(VirtualCollection.class,
148                    Long.valueOf(id), LockModeType.PESSIMISTIC_WRITE);
149            /*
150             * Do not check for deleted state here, as we might want to
151             * resurrect deleted virtual collections.
152             */
153            if (c == null) {
154                logger.debug("virtual collection (id={}) not found", id);
155                throw new VirtualCollectionNotFoundException(id);
156            }
157            if (!c.getOwner().equalsPrincipal(principal)) {
158                throw new VirtualCollectionRegistryPermissionException(
159                        "permission denied for user \""
160                        + principal.getName() + "\"");
161            }
162
163            // update virtual collection
164            c.updateFrom(vc);
165
166            validator.validate(c);
167            em.merge(c);
168            em.getTransaction().commit();
169            logger.debug("updated virtual collection (id={})", vc.getId());
170            return c.getId();
171        } catch (VirtualCollectionRegistryException e) {
172            logger.debug("failed updating virtual collecion (id={}): {}", id,
173                    e.getMessage());
174            throw e;
175        } catch (Exception e) {
176            logger.error("error while updating virtual collection", e);
177            throw new VirtualCollectionRegistryException(
178                    "error while updating virtual collection", e);
179        }
180    }
181
182    public long deleteVirtualCollection(Principal principal, long id)
183            throws VirtualCollectionRegistryException {
184        if (principal == null) {
185            throw new NullPointerException("principal == null");
186        }
187        if (id <= 0) {
188            throw new IllegalArgumentException("id <= 0");
189        }
190
191        logger.debug("deleting virtual collection (id={})", id);
192
193        try {
194            EntityManager em = datastore.getEntityManager();
195            em.getTransaction().begin();
196            VirtualCollection vc = em.find(VirtualCollection.class,
197                    Long.valueOf(id), LockModeType.PESSIMISTIC_WRITE);
198            if ((vc == null) || vc.isDeleted()) {
199                logger.debug("virtual collection (id={}) not found", id);
200                throw new VirtualCollectionNotFoundException(id);
201            }
202            if (!vc.getOwner().equalsPrincipal(principal)) {
203                logger.debug("virtual collection (id={}) not owned by "
204                        + "user '{}'", id, principal.getName());
205                throw new VirtualCollectionRegistryPermissionException(
206                        "permission denied for user \""
207                        + principal.getName() + "\"");
208            }
209            if (!vc.isPrivate()) {
210                logger.debug("virtual collection (id={}) cannot be "
211                        + "deleted (invalid state)", id);
212                throw new VirtualCollectionRegistryPermissionException(
213                        "virtual collection cannot be deleted");
214            }
215            vc.setState(VirtualCollection.State.DELETED);
216            em.getTransaction().commit();
217            return vc.getId();
218        } catch (VirtualCollectionRegistryException e) {
219            logger.debug("failed deleting virtual collecion (id={}): {}", id,
220                    e.getMessage());
221            throw e;
222        } catch (Exception e) {
223            logger.error("error while deleting virtual collection", e);
224            throw new VirtualCollectionRegistryException(
225                    "error while deleting virtual collection", e);
226        }
227    }
228
229    public VirtualCollection.State getVirtualCollectionState(long id)
230            throws VirtualCollectionRegistryException {
231        if (id <= 0) {
232            throw new IllegalArgumentException("id <= 0");
233        }
234
235        logger.debug("retrieve virtual collection state (id={})", id);
236
237        try {
238            EntityManager em = datastore.getEntityManager();
239            em.getTransaction().begin();
240            VirtualCollection vc
241                    = em.find(VirtualCollection.class, Long.valueOf(id));
242            em.getTransaction().commit();
243            if ((vc == null) || vc.isDeleted()) {
244                logger.debug("virtual collection (id={}) not found", id);
245                throw new VirtualCollectionNotFoundException(id);
246            }
247            return vc.getState();
248        } catch (VirtualCollectionRegistryException e) {
249            throw e;
250        } catch (Exception e) {
251            logger.error(
252                    "error while retrieving state of virtual collection", e);
253            throw new VirtualCollectionRegistryException(
254                    "error while retrieving state of virtual collection", e);
255        }
256    }
257
258    public void setVirtualCollectionState(Principal principal, long id,
259            VirtualCollection.State state)
260            throws VirtualCollectionRegistryException {
261        if (principal == null) {
262            throw new NullPointerException("principal == null");
263        }
264        if (id <= 0) {
265            throw new IllegalArgumentException("id <= 0");
266        }
267        if (state == null) {
268            throw new NullPointerException("state == null");
269        }
270        if ((state != VirtualCollection.State.PUBLIC_PENDING)
271                && (state != VirtualCollection.State.PRIVATE)) {
272            throw new IllegalArgumentException(
273                    "only PUBLIC_PENDING or PRIVATE are allowed");
274        }
275
276        logger.debug("setting state virtual collection state (id={}) to '{}'",
277                id, state);
278
279        try {
280            EntityManager em = datastore.getEntityManager();
281            em.getTransaction().begin();
282            VirtualCollection vc = em.find(VirtualCollection.class,
283                    Long.valueOf(id), LockModeType.PESSIMISTIC_WRITE);
284            if ((vc == null) || vc.isDeleted()) {
285                logger.debug("virtual collection (id={}) not found", id);
286                throw new VirtualCollectionNotFoundException(id);
287            }
288            if (!vc.getOwner().equalsPrincipal(principal)) {
289                logger.debug("virtual collection (id={}) not owned by "
290                        + "user '{}'", id, principal.getName());
291                throw new VirtualCollectionRegistryPermissionException(
292                        "permission denied for user \""
293                        + principal.getName() + "\"");
294            }
295
296            /*
297             * XXX: deny update from public to private?
298             */
299            boolean update = false;
300            switch (state) {
301                case PRIVATE:
302                    update = vc.getState() != state;
303                    break;
304                case PUBLIC_PENDING:
305                    update = vc.getState() != VirtualCollection.State.PUBLIC;
306                    break;
307                default:
308                    /* silence warning; update will stay false */
309                    break;
310            }
311            if (update) {
312                vc.setState(state);
313                em.persist(vc);
314            }
315            em.getTransaction().commit();
316        } catch (VirtualCollectionRegistryException e) {
317            throw e;
318        } catch (Exception e) {
319            logger.error(
320                    "error while setting state of virtual collection", e);
321            throw new VirtualCollectionRegistryException(
322                    "error while setting state of virtual collection", e);
323        }
324    }
325
326    /**
327     *
328     * @param id identifier of the virtual collection to retrieve
329     * @return the identified virtual collection, never null
330     * @throws VirtualCollectionRegistryException if no virtual collection with
331     * the specified identifier exists
332     */
333    public VirtualCollection retrieveVirtualCollection(long id)
334            throws VirtualCollectionRegistryException {
335        if (id <= 0) {
336            throw new IllegalArgumentException("id <= 0");
337        }
338
339        logger.debug("retrieve virtual collection (id={})", id);
340
341        try {
342            EntityManager em = datastore.getEntityManager();
343            em.getTransaction().begin();
344            VirtualCollection vc
345                    = em.find(VirtualCollection.class, Long.valueOf(id));
346            em.getTransaction().commit();
347            if ((vc == null) || vc.isDeleted()) {
348                logger.debug("virtual collection (id={}) not found", id);
349                throw new VirtualCollectionNotFoundException(id);
350            }
351            logger.debug("virtual collection retrieved (id={})", id);
352            return vc;
353        } catch (VirtualCollectionRegistryException e) {
354            throw e;
355        } catch (Exception e) {
356            logger.error("error while retrieving virtual collection", e);
357            throw new VirtualCollectionRegistryException(
358                    "error while retrieving virtual collection", e);
359        }
360    }
361
362    public VirtualCollectionList getVirtualCollections(String query,
363            int offset, int count) throws VirtualCollectionRegistryException {
364        EntityManager em = datastore.getEntityManager();
365        try {
366            em.getTransaction().begin();
367
368            // setup queries
369            TypedQuery<Long> cq = null;
370            TypedQuery<VirtualCollection> q = null;
371            if (query != null) {
372                ParsedQuery parsedQuery = ParsedQuery.parseQuery(query);
373                if (logger.isDebugEnabled()) {
374                    logger.debug(parsedQuery.getPrettyPrinted());
375                }
376                cq = parsedQuery.getCountQuery(em, null, VirtualCollection.State.PUBLIC);
377                q = parsedQuery.getQuery(em, null, VirtualCollection.State.PUBLIC);
378            } else {
379                cq = em.createNamedQuery("VirtualCollection.countAllPublic",
380                        Long.class);
381                q = em.createNamedQuery("VirtualCollection.findAllPublic",
382                        VirtualCollection.class);
383            }
384
385            // commence query ...
386            List<VirtualCollection> results = null;
387            long totalCount = cq.getSingleResult();
388
389            // optimization; don't query, if we won't get any results
390            /*
391             *  FIXME: offset == -1 is temporary hack for just fetching
392             *  total count; re-factor to have fetch-count and fetch-data
393             *  methods!
394             */
395            if ((totalCount > 0) && (offset > -1)) {
396                if (offset > 0) {
397                    q.setFirstResult(offset);
398                }
399                if (count > 0) {
400                    q.setMaxResults(count);
401                }
402                results = q.getResultList();
403            }
404            return new VirtualCollectionList(results, offset, (int) totalCount);
405        } catch (Exception e) {
406            logger.error("error while enumerating virtual collections", e);
407            throw new VirtualCollectionRegistryException(
408                    "error while enumerating virtual collections", e);
409        } finally {
410            EntityTransaction tx = em.getTransaction();
411            if ((tx != null) && !tx.getRollbackOnly()) {
412                tx.commit();
413            }
414        }
415    }
416
417    public VirtualCollectionList getVirtualCollections(Principal principal,
418            String query, int offset, int count)
419            throws VirtualCollectionRegistryException {
420        if (principal == null) {
421            throw new NullPointerException("principal == null");
422        }
423        EntityManager em = datastore.getEntityManager();
424        try {
425            List<VirtualCollection> results = null;
426            long totalCount = 0;
427
428            em.getTransaction().begin();
429
430            /*
431             * fetch user. if user is not found, he has not yet registered any
432             * virtual collections, so just return an empty list
433             */
434            User user = fetchUser(em, principal);
435            if (user != null) {
436                // setup queries
437                TypedQuery<Long> cq = null;
438                TypedQuery<VirtualCollection> q = null;
439                if (query != null) {
440                    ParsedQuery parsedQuery = ParsedQuery.parseQuery(query);
441                    if (logger.isDebugEnabled()) {
442                        logger.debug(parsedQuery.getPrettyPrinted());
443                    }
444                    cq = parsedQuery.getCountQuery(em, user, null);
445                    q = parsedQuery.getQuery(em, user, null);
446                } else {
447                    cq = em.createNamedQuery("VirtualCollection.countByOwner",
448                            Long.class);
449                    cq.setParameter("owner", user);
450                    q = em.createNamedQuery("VirtualCollection.findByOwner",
451                            VirtualCollection.class);
452                    q.setParameter("owner", user);
453                }
454
455                // commence query ...
456                totalCount = cq.getSingleResult();
457
458                // optimization; don't query, if we won't get any results
459                /*
460                 *  FIXME: offset == -1 is temporary hack for just fetching
461                 *  total count; re-factor to have fetch-count and fetch-data
462                 *  methods!
463                 */
464                if ((totalCount > 0) && (offset > -1)) {
465                    if (offset > 0) {
466                        q.setFirstResult(offset);
467                    }
468                    if (count > 0) {
469                        q.setMaxResults(count);
470                    }
471                    results = q.getResultList();
472                }
473            }
474            return new VirtualCollectionList(results, offset, (int) totalCount);
475        } catch (Exception e) {
476            logger.error("error while enumerating virtual collections", e);
477            throw new VirtualCollectionRegistryException(
478                    "error while enumerating virtual collections", e);
479        } finally {
480            EntityTransaction tx = em.getTransaction();
481            if ((tx != null) && !tx.getRollbackOnly()) {
482                tx.commit();
483            }
484        }
485    }
486
487    public int getVirtualCollectionCount(QueryOptions options)
488            throws VirtualCollectionRegistryException {
489        logger.trace("Getting virtual collection count");
490        EntityManager em = datastore.getEntityManager();
491        try {
492            CriteriaBuilder cb = em.getCriteriaBuilder();
493            CriteriaQuery<Long> cq = cb.createQuery(Long.class);
494            Root<VirtualCollection> root = cq.from(VirtualCollection.class);
495            if (options != null) {
496                Predicate where = options.getWhere(cb, cq, root);
497                if (where != null) {
498                    cq.where(where);
499                }
500            }
501            em.getTransaction().begin();
502            TypedQuery<Long> query
503                    = em.createQuery(cq.select(cb.count(root)));
504            final long count = query.getSingleResult();
505            if (count >= Integer.MAX_VALUE) {
506                throw new VirtualCollectionRegistryException(
507                        "resultset too large");
508            }
509            logger.trace("Counted {} collections", count);
510            return (int) count;
511        } catch (Exception e) {
512            logger.error("error while counting virtual collections", e);
513            throw new VirtualCollectionRegistryException(
514                    "error while counting virtual collections", e);
515        } finally {
516            EntityTransaction tx = em.getTransaction();
517            if ((tx != null) && tx.isActive() && !tx.getRollbackOnly()) {
518                tx.commit();
519            }
520        }
521    }
522
523    public List<VirtualCollection> getVirtualCollections(
524            int first, int count, QueryOptions options)
525            throws VirtualCollectionRegistryException {
526        EntityManager em = datastore.getEntityManager();
527        try {
528            CriteriaBuilder cb = em.getCriteriaBuilder();
529            CriteriaQuery<VirtualCollection> cq
530                    = cb.createQuery(VirtualCollection.class);
531            Root<VirtualCollection> root = cq.from(VirtualCollection.class);
532            if (options != null) {
533                final Predicate where = options.getWhere(cb, cq, root);
534                if (where != null) {
535                    cq.where(where);
536                }
537                final Order[] order = options.getOrderBy(cb, root);
538                if (order != null) {
539                    cq.orderBy(order);
540                }
541            }
542            em.getTransaction().begin();
543            TypedQuery<VirtualCollection> query
544                    = em.createQuery(cq.select(root));
545            if (first > -1) {
546                query.setFirstResult(first);
547            }
548            if (count > 0) {
549                query.setMaxResults(count);
550            }
551            return query.getResultList();
552        } catch (Exception e) {
553            logger.error("error while fetching virtual collections", e);
554            throw new VirtualCollectionRegistryException(
555                    "error while fetching virtual collections", e);
556        } finally {
557            EntityTransaction tx = em.getTransaction();
558            if ((tx != null) && tx.isActive() && !tx.getRollbackOnly()) {
559                tx.commit();
560            }
561        }
562    }
563
564    private void maintenance(long now) {
565        logger.debug("Maintenance check");
566        // allocate persistent identifier roughly after 30 seconds
567        final Date nowDateAlloc = new Date(now - 30 * 1000);
568        // (for now) purge deleted collection roughly after 30 seconds
569        final Date nowDatePurge = new Date(now - 30 * 1000);
570
571        EntityManager em = datastore.getEntityManager();
572        try {
573            /*
574             * delayed allocation of persistent identifier
575             */
576            em.getTransaction().begin();
577            TypedQuery<VirtualCollection> q
578                    = em.createNamedQuery("VirtualCollection.findAllByState",
579                            VirtualCollection.class);
580            q.setParameter("state", VirtualCollection.State.PUBLIC_PENDING);
581            q.setParameter("date", nowDateAlloc);
582            q.setLockMode(LockModeType.PESSIMISTIC_WRITE);
583            for (VirtualCollection vc : q.getResultList()) {
584                if (vc.getPersistentIdentifier() == null) {
585                    PersistentIdentifier pid = pid_provider.createIdentifier(vc);
586                    vc.setPersistentIdentifier(pid);
587                }
588                vc.setState(VirtualCollection.State.PUBLIC);
589                em.persist(vc);
590                logger.info("assigned pid (identifer='{}') to virtual"
591                        + "collection (id={})",
592                        vc.getPersistentIdentifier().getIdentifier(),
593                        vc.getId());
594            }
595            em.getTransaction().commit();
596
597            /*
598             * delayed purging of deleted virtual collections
599             */
600            em.getTransaction().begin();
601            q.setParameter("state", VirtualCollection.State.DELETED);
602            q.setParameter("date", nowDatePurge);
603            q.setLockMode(LockModeType.PESSIMISTIC_WRITE);
604            for (VirtualCollection vc : q.getResultList()) {
605                vc.setState(VirtualCollection.State.DEAD);
606                em.remove(vc);
607                logger.debug("purged virtual collection (id={})", vc.getId());
608            }
609            em.getTransaction().commit();
610        } catch (Exception e) {
611            logger.error("error while doing maintenance", e);
612        } finally {
613            datastore.closeEntityManager();
614        }
615    }
616
617    private static User fetchUser(EntityManager em, Principal principal) {
618        User user = null;
619        try {
620            TypedQuery<User> q
621                    = em.createNamedQuery("User.findByName", User.class);
622            q.setParameter("name", principal.getName());
623            user = q.getSingleResult();
624        } catch (NoResultException e) {
625            /* IGNORE */
626        }
627        return user;
628    }
629
630} // class VirtualCollectionRegistry
Note: See TracBrowser for help on using the repository browser.