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