diff --git a/docs-core/src/main/java/com/sismics/docs/core/service/IndexingService.java b/docs-core/src/main/java/com/sismics/docs/core/service/IndexingService.java index 7f7fbeed..5502af52 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/service/IndexingService.java +++ b/docs-core/src/main/java/com/sismics/docs/core/service/IndexingService.java @@ -1,9 +1,11 @@ package com.sismics.docs.core.service; -import java.io.IOException; -import java.nio.file.Path; -import java.util.concurrent.TimeUnit; - +import com.google.common.util.concurrent.AbstractScheduledService; +import com.sismics.docs.core.constant.Constants; +import com.sismics.docs.core.event.RebuildIndexAsyncEvent; +import com.sismics.docs.core.model.context.AppContext; +import com.sismics.docs.core.util.DirectoryUtil; +import com.sismics.docs.core.util.TransactionUtil; import org.apache.lucene.index.CheckIndex; import org.apache.lucene.index.CheckIndex.Status; import org.apache.lucene.index.CheckIndex.Status.SegmentInfoStatus; @@ -16,12 +18,9 @@ import org.apache.lucene.util.Version; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.util.concurrent.AbstractScheduledService; -import com.sismics.docs.core.constant.Constants; -import com.sismics.docs.core.event.RebuildIndexAsyncEvent; -import com.sismics.docs.core.model.context.AppContext; -import com.sismics.docs.core.util.DirectoryUtil; -import com.sismics.docs.core.util.TransactionUtil; +import java.io.IOException; +import java.nio.file.Path; +import java.util.concurrent.TimeUnit; /** * Indexing service. @@ -129,16 +128,6 @@ public class IndexingService extends AbstractScheduledService { return Scheduler.newFixedDelaySchedule(0, 1, TimeUnit.HOURS); } - /** - * Destroy and rebuild Lucene index. - * - * @throws Exception - */ - public void rebuildIndex() throws Exception { - RebuildIndexAsyncEvent rebuildIndexAsyncEvent = new RebuildIndexAsyncEvent(); - AppContext.getInstance().getAsyncEventBus().post(rebuildIndexAsyncEvent); - } - /** * Getter of directory. * diff --git a/docs-core/src/main/java/com/sismics/docs/core/util/TransactionUtil.java b/docs-core/src/main/java/com/sismics/docs/core/util/TransactionUtil.java index a8ab3a37..f92b4b2d 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/util/TransactionUtil.java +++ b/docs-core/src/main/java/com/sismics/docs/core/util/TransactionUtil.java @@ -22,12 +22,12 @@ public class TransactionUtil { /** * Encapsulate a process into a transactionnal context. * - * @param runnable + * @param runnable Runnable */ public static void handle(Runnable runnable) { EntityManager em = ThreadLocalContext.get().getEntityManager(); - if (em != null) { + if (em != null && em.isOpen()) { // We are already in a transactional context, nothing to do runnable.run(); return; diff --git a/docs-core/src/main/java/com/sismics/util/context/ThreadLocalContext.java b/docs-core/src/main/java/com/sismics/util/context/ThreadLocalContext.java index 71ff24f1..4270c627 100644 --- a/docs-core/src/main/java/com/sismics/util/context/ThreadLocalContext.java +++ b/docs-core/src/main/java/com/sismics/util/context/ThreadLocalContext.java @@ -1,6 +1,10 @@ package com.sismics.util.context; +import com.google.common.collect.Lists; +import com.sismics.docs.core.model.context.AppContext; + import javax.persistence.EntityManager; +import java.util.List; /** * Context associated to a user request, and stored in a ThreadLocal. @@ -17,7 +21,12 @@ public class ThreadLocalContext { * Entity manager. */ private EntityManager entityManager; - + + /** + * List of async events posted during this request. + */ + private List asyncEventList = Lists.newArrayList(); + /** * Private constructor. */ @@ -63,4 +72,22 @@ public class ThreadLocalContext { public void setEntityManager(EntityManager entityManager) { this.entityManager = entityManager; } + + /** + * Add an async event to the queue to be fired after the current request. + * + * @param asyncEvent Async event + */ + public void addAsyncEvent(Object asyncEvent) { + asyncEventList.add(asyncEvent); + } + + /** + * Fire all pending async events. + */ + public void fireAllAsyncEvents() { + for (Object asyncEvent : asyncEventList) { + AppContext.getInstance().getAsyncEventBus().post(asyncEvent); + } + } } diff --git a/docs-web-common/src/main/java/com/sismics/util/filter/RequestContextFilter.java b/docs-web-common/src/main/java/com/sismics/util/filter/RequestContextFilter.java index 6acdef32..58eb6bc8 100644 --- a/docs-web-common/src/main/java/com/sismics/util/filter/RequestContextFilter.java +++ b/docs-web-common/src/main/java/com/sismics/util/filter/RequestContextFilter.java @@ -140,7 +140,11 @@ public class RequestContextFilter implements Filter { } } } - + + // Fire all pending async events after request transaction commit. + // This way, all modifications done during this request are available in the listeners. + context.fireAllAsyncEvents(); + ThreadLocalContext.cleanup(); } } diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/AppResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/AppResource.java index ae420612..3a99c08d 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/AppResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/AppResource.java @@ -23,6 +23,7 @@ import com.sismics.docs.core.dao.jpa.*; import com.sismics.docs.core.dao.jpa.criteria.TagCriteria; import com.sismics.docs.core.dao.jpa.dto.AclDto; import com.sismics.docs.core.dao.jpa.dto.TagDto; +import com.sismics.docs.core.event.RebuildIndexAsyncEvent; import com.sismics.docs.core.model.jpa.Acl; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Appender; @@ -213,12 +214,9 @@ public class AppResource extends BaseResource { } checkBaseFunction(BaseFunction.ADMIN); - try { - AppContext.getInstance().getIndexingService().rebuildIndex(); - } catch (Exception e) { - throw new ServerException("IndexingError", "Error rebuilding the index", e); - } - + RebuildIndexAsyncEvent rebuildIndexAsyncEvent = new RebuildIndexAsyncEvent(); + ThreadLocalContext.get().addAsyncEvent(rebuildIndexAsyncEvent); + // Always return OK JsonObjectBuilder response = Json.createObjectBuilder() .add("status", "ok"); diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java index 918725fa..32a3d5b8 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java @@ -28,6 +28,7 @@ import com.sismics.rest.exception.ServerException; import com.sismics.rest.util.AclUtil; import com.sismics.rest.util.JsonUtil; import com.sismics.rest.util.ValidationUtil; +import com.sismics.util.context.ThreadLocalContext; import com.sismics.util.mime.MimeType; import org.joda.time.DateTime; import org.joda.time.format.DateTimeFormat; @@ -636,8 +637,8 @@ public class DocumentResource extends BaseResource { DocumentCreatedAsyncEvent documentCreatedAsyncEvent = new DocumentCreatedAsyncEvent(); documentCreatedAsyncEvent.setUserId(principal.getId()); documentCreatedAsyncEvent.setDocument(document); - AppContext.getInstance().getAsyncEventBus().post(documentCreatedAsyncEvent); - + ThreadLocalContext.get().addAsyncEvent(documentCreatedAsyncEvent); + JsonObjectBuilder response = Json.createObjectBuilder() .add("id", documentId); return Response.ok().entity(response.build()).build(); @@ -757,7 +758,7 @@ public class DocumentResource extends BaseResource { DocumentUpdatedAsyncEvent documentUpdatedAsyncEvent = new DocumentUpdatedAsyncEvent(); documentUpdatedAsyncEvent.setUserId(principal.getId()); documentUpdatedAsyncEvent.setDocumentId(id); - AppContext.getInstance().getAsyncEventBus().post(documentUpdatedAsyncEvent); + ThreadLocalContext.get().addAsyncEvent(documentUpdatedAsyncEvent); JsonObjectBuilder response = Json.createObjectBuilder() .add("id", id); @@ -852,14 +853,14 @@ public class DocumentResource extends BaseResource { FileDeletedAsyncEvent fileDeletedAsyncEvent = new FileDeletedAsyncEvent(); fileDeletedAsyncEvent.setUserId(principal.getId()); fileDeletedAsyncEvent.setFile(file); - AppContext.getInstance().getAsyncEventBus().post(fileDeletedAsyncEvent); + ThreadLocalContext.get().addAsyncEvent(fileDeletedAsyncEvent); } // Raise a document deleted event DocumentDeletedAsyncEvent documentDeletedAsyncEvent = new DocumentDeletedAsyncEvent(); documentDeletedAsyncEvent.setUserId(principal.getId()); documentDeletedAsyncEvent.setDocumentId(id); - AppContext.getInstance().getAsyncEventBus().post(documentDeletedAsyncEvent); + ThreadLocalContext.get().addAsyncEvent(documentDeletedAsyncEvent); // Always return OK JsonObjectBuilder response = Json.createObjectBuilder() diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/FileResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/FileResource.java index cd4e9b46..f27c87f7 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/FileResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/FileResource.java @@ -1,30 +1,5 @@ package com.sismics.docs.rest.resource; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.text.MessageFormat; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.List; -import java.util.zip.ZipEntry; -import java.util.zip.ZipOutputStream; - -import javax.json.Json; -import javax.json.JsonArrayBuilder; -import javax.json.JsonObjectBuilder; -import javax.ws.rs.*; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.Response.Status; -import javax.ws.rs.core.StreamingOutput; - -import org.glassfish.jersey.media.multipart.FormDataBodyPart; -import org.glassfish.jersey.media.multipart.FormDataParam; - import com.google.common.base.Strings; import com.google.common.collect.Lists; import com.google.common.io.ByteStreams; @@ -37,7 +12,6 @@ import com.sismics.docs.core.dao.jpa.dto.DocumentDto; import com.sismics.docs.core.event.DocumentUpdatedAsyncEvent; import com.sismics.docs.core.event.FileCreatedAsyncEvent; import com.sismics.docs.core.event.FileDeletedAsyncEvent; -import com.sismics.docs.core.model.context.AppContext; import com.sismics.docs.core.model.jpa.File; import com.sismics.docs.core.model.jpa.User; import com.sismics.docs.core.util.DirectoryUtil; @@ -49,8 +23,32 @@ import com.sismics.rest.exception.ForbiddenClientException; import com.sismics.rest.exception.ServerException; import com.sismics.rest.util.JsonUtil; import com.sismics.rest.util.ValidationUtil; +import com.sismics.util.context.ThreadLocalContext; import com.sismics.util.mime.MimeType; import com.sismics.util.mime.MimeTypeUtil; +import org.glassfish.jersey.media.multipart.FormDataBodyPart; +import org.glassfish.jersey.media.multipart.FormDataParam; + +import javax.json.Json; +import javax.json.JsonArrayBuilder; +import javax.json.JsonObjectBuilder; +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; +import javax.ws.rs.core.StreamingOutput; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.text.MessageFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; /** * File REST resources. @@ -179,12 +177,12 @@ public class FileResource extends BaseResource { fileCreatedAsyncEvent.setFile(file); fileCreatedAsyncEvent.setInputStream(fileInputStream); fileCreatedAsyncEvent.setPdfInputStream(pdfIntputStream); - AppContext.getInstance().getAsyncEventBus().post(fileCreatedAsyncEvent); + ThreadLocalContext.get().addAsyncEvent(fileCreatedAsyncEvent); DocumentUpdatedAsyncEvent documentUpdatedAsyncEvent = new DocumentUpdatedAsyncEvent(); documentUpdatedAsyncEvent.setUserId(principal.getId()); documentUpdatedAsyncEvent.setDocumentId(documentId); - AppContext.getInstance().getAsyncEventBus().post(documentUpdatedAsyncEvent); + ThreadLocalContext.get().addAsyncEvent(documentUpdatedAsyncEvent); } // Always return OK @@ -262,12 +260,12 @@ public class FileResource extends BaseResource { fileCreatedAsyncEvent.setLanguage(documentDto.getLanguage()); fileCreatedAsyncEvent.setFile(file); fileCreatedAsyncEvent.setInputStream(responseInputStream); - AppContext.getInstance().getAsyncEventBus().post(fileCreatedAsyncEvent); + ThreadLocalContext.get().addAsyncEvent(fileCreatedAsyncEvent); DocumentUpdatedAsyncEvent documentUpdatedAsyncEvent = new DocumentUpdatedAsyncEvent(); documentUpdatedAsyncEvent.setUserId(principal.getId()); documentUpdatedAsyncEvent.setDocumentId(documentId); - AppContext.getInstance().getAsyncEventBus().post(documentUpdatedAsyncEvent); + ThreadLocalContext.get().addAsyncEvent(documentUpdatedAsyncEvent); } catch (Exception e) { throw new ServerException("AttachError", "Error attaching file to document", e); } @@ -455,14 +453,14 @@ public class FileResource extends BaseResource { FileDeletedAsyncEvent fileDeletedAsyncEvent = new FileDeletedAsyncEvent(); fileDeletedAsyncEvent.setUserId(principal.getId()); fileDeletedAsyncEvent.setFile(file); - AppContext.getInstance().getAsyncEventBus().post(fileDeletedAsyncEvent); + ThreadLocalContext.get().addAsyncEvent(fileDeletedAsyncEvent); if (file.getDocumentId() != null) { // Raise a new document updated DocumentUpdatedAsyncEvent documentUpdatedAsyncEvent = new DocumentUpdatedAsyncEvent(); documentUpdatedAsyncEvent.setUserId(principal.getId()); documentUpdatedAsyncEvent.setDocumentId(file.getDocumentId()); - AppContext.getInstance().getAsyncEventBus().post(documentUpdatedAsyncEvent); + ThreadLocalContext.get().addAsyncEvent(documentUpdatedAsyncEvent); } // Always return OK diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/UserResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/UserResource.java index cdfc1e2b..731488c4 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/UserResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/UserResource.java @@ -1,52 +1,18 @@ package com.sismics.docs.rest.resource; -import java.security.NoSuchAlgorithmException; -import java.util.Date; -import java.util.List; -import java.util.Set; - -import javax.json.Json; -import javax.json.JsonArrayBuilder; -import javax.json.JsonObjectBuilder; -import javax.servlet.http.Cookie; -import javax.ws.rs.DELETE; -import javax.ws.rs.FormParam; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.NewCookie; -import javax.ws.rs.core.Response; - -import com.sismics.docs.core.constant.ConfigType; -import com.sismics.docs.core.util.ConfigUtil; -import org.apache.commons.lang.StringUtils; - import com.google.common.base.Strings; import com.google.common.collect.Sets; +import com.sismics.docs.core.constant.ConfigType; import com.sismics.docs.core.constant.Constants; -import com.sismics.docs.core.dao.jpa.AuthenticationTokenDao; -import com.sismics.docs.core.dao.jpa.DocumentDao; -import com.sismics.docs.core.dao.jpa.FileDao; -import com.sismics.docs.core.dao.jpa.GroupDao; -import com.sismics.docs.core.dao.jpa.RoleBaseFunctionDao; -import com.sismics.docs.core.dao.jpa.UserDao; +import com.sismics.docs.core.dao.jpa.*; import com.sismics.docs.core.dao.jpa.criteria.GroupCriteria; import com.sismics.docs.core.dao.jpa.criteria.UserCriteria; import com.sismics.docs.core.dao.jpa.dto.GroupDto; import com.sismics.docs.core.dao.jpa.dto.UserDto; import com.sismics.docs.core.event.DocumentDeletedAsyncEvent; import com.sismics.docs.core.event.FileDeletedAsyncEvent; -import com.sismics.docs.core.model.context.AppContext; -import com.sismics.docs.core.model.jpa.AuthenticationToken; -import com.sismics.docs.core.model.jpa.Document; -import com.sismics.docs.core.model.jpa.File; -import com.sismics.docs.core.model.jpa.Group; -import com.sismics.docs.core.model.jpa.User; +import com.sismics.docs.core.model.jpa.*; +import com.sismics.docs.core.util.ConfigUtil; import com.sismics.docs.core.util.EncryptionUtil; import com.sismics.docs.core.util.jpa.SortCriteria; import com.sismics.docs.rest.constant.BaseFunction; @@ -56,9 +22,24 @@ import com.sismics.rest.exception.ServerException; import com.sismics.rest.util.JsonUtil; import com.sismics.rest.util.ValidationUtil; import com.sismics.security.UserPrincipal; +import com.sismics.util.context.ThreadLocalContext; import com.sismics.util.filter.TokenBasedSecurityFilter; import com.sismics.util.totp.GoogleAuthenticator; import com.sismics.util.totp.GoogleAuthenticatorKey; +import org.apache.commons.lang.StringUtils; + +import javax.json.Json; +import javax.json.JsonArrayBuilder; +import javax.json.JsonObjectBuilder; +import javax.servlet.http.Cookie; +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.NewCookie; +import javax.ws.rs.core.Response; +import java.security.NoSuchAlgorithmException; +import java.util.Date; +import java.util.List; +import java.util.Set; /** * User REST resources. @@ -472,7 +453,7 @@ public class UserResource extends BaseResource { DocumentDeletedAsyncEvent documentDeletedAsyncEvent = new DocumentDeletedAsyncEvent(); documentDeletedAsyncEvent.setUserId(principal.getId()); documentDeletedAsyncEvent.setDocumentId(document.getId()); - AppContext.getInstance().getAsyncEventBus().post(documentDeletedAsyncEvent); + ThreadLocalContext.get().addAsyncEvent(documentDeletedAsyncEvent); } // Raise deleted events for files (don't bother sending document updated event) @@ -480,7 +461,7 @@ public class UserResource extends BaseResource { FileDeletedAsyncEvent fileDeletedAsyncEvent = new FileDeletedAsyncEvent(); fileDeletedAsyncEvent.setUserId(principal.getId()); fileDeletedAsyncEvent.setFile(file); - AppContext.getInstance().getAsyncEventBus().post(fileDeletedAsyncEvent); + ThreadLocalContext.get().addAsyncEvent(fileDeletedAsyncEvent); } // Always return OK @@ -546,7 +527,7 @@ public class UserResource extends BaseResource { DocumentDeletedAsyncEvent documentDeletedAsyncEvent = new DocumentDeletedAsyncEvent(); documentDeletedAsyncEvent.setUserId(principal.getId()); documentDeletedAsyncEvent.setDocumentId(document.getId()); - AppContext.getInstance().getAsyncEventBus().post(documentDeletedAsyncEvent); + ThreadLocalContext.get().addAsyncEvent(documentDeletedAsyncEvent); } // Raise deleted events for files (don't bother sending document updated event) @@ -554,7 +535,7 @@ public class UserResource extends BaseResource { FileDeletedAsyncEvent fileDeletedAsyncEvent = new FileDeletedAsyncEvent(); fileDeletedAsyncEvent.setUserId(principal.getId()); fileDeletedAsyncEvent.setFile(file); - AppContext.getInstance().getAsyncEventBus().post(fileDeletedAsyncEvent); + ThreadLocalContext.get().addAsyncEvent(fileDeletedAsyncEvent); } // Always return OK