docs/docs-web/src/main/java/com/sismics/docs/rest/resource/AppResource.java

464 lines
20 KiB
Java
Raw Normal View History

2013-07-27 18:33:20 +02:00
package com.sismics.docs.rest.resource;
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
2015-12-01 01:16:57 +01:00
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
2015-09-07 21:51:13 +02:00
import javax.json.Json;
import javax.json.JsonArrayBuilder;
import javax.json.JsonObjectBuilder;
import javax.persistence.EntityManager;
import javax.persistence.Query;
2016-05-28 23:09:52 +02:00
import javax.ws.rs.*;
import javax.ws.rs.core.Response;
2016-05-28 23:09:52 +02:00
import com.sismics.docs.core.constant.ConfigType;
import com.sismics.docs.core.constant.PermType;
2016-05-28 23:09:52 +02:00
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.model.jpa.Acl;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Appender;
import org.apache.log4j.Level;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.sismics.docs.core.model.context.AppContext;
import com.sismics.docs.core.model.jpa.File;
import com.sismics.docs.core.model.jpa.User;
2013-07-27 18:33:20 +02:00
import com.sismics.docs.core.util.ConfigUtil;
import com.sismics.docs.core.util.DirectoryUtil;
2013-07-27 18:33:20 +02:00
import com.sismics.docs.core.util.jpa.PaginatedList;
import com.sismics.docs.core.util.jpa.PaginatedLists;
import com.sismics.docs.rest.constant.BaseFunction;
import com.sismics.rest.exception.ForbiddenClientException;
import com.sismics.rest.exception.ServerException;
import com.sismics.util.context.ThreadLocalContext;
2013-07-27 18:33:20 +02:00
import com.sismics.util.log4j.LogCriteria;
import com.sismics.util.log4j.LogEntry;
import com.sismics.util.log4j.MemoryAppender;
/**
* General app REST resource.
*
* @author jtremeaux
*/
@Path("/app")
public class AppResource extends BaseResource {
/**
* Logger.
*/
private static final Logger log = LoggerFactory.getLogger(AppResource.class);
2013-07-27 18:33:20 +02:00
/**
* Returns informations about the application.
*
* @api {get} /app Get application informations
* @apiName GetApp
* @apiGroup App
* @apiSuccess {String} current_version API current version
* @apiSuccess {String} min_version API minimum version
2016-05-28 23:09:52 +02:00
* @apiSuccess {Boolean} guest_login True if guest login is enabled
* @apiSuccess {String} total_memory Allocated JVM memory (in bytes)
* @apiSuccess {String} free_memory Free JVM memory (in bytes)
2016-05-28 23:09:52 +02:00
* @apiPermission none
* @apiVersion 1.5.0
*
2013-07-27 18:33:20 +02:00
* @return Response
*/
@GET
2015-09-07 21:51:13 +02:00
public Response info() {
2013-07-27 18:33:20 +02:00
ResourceBundle configBundle = ConfigUtil.getConfigBundle();
String currentVersion = configBundle.getString("api.current_version");
String minVersion = configBundle.getString("api.min_version");
2016-05-28 23:09:52 +02:00
Boolean guestLogin = ConfigUtil.getConfigBooleanValue(ConfigType.GUEST_LOGIN);
2013-07-27 18:33:20 +02:00
2015-09-07 21:51:13 +02:00
JsonObjectBuilder response = Json.createObjectBuilder()
.add("current_version", currentVersion.replace("-SNAPSHOT", ""))
.add("min_version", minVersion)
2016-05-28 23:09:52 +02:00
.add("guest_login", guestLogin)
2015-09-07 21:51:13 +02:00
.add("total_memory", Runtime.getRuntime().totalMemory())
.add("free_memory", Runtime.getRuntime().freeMemory());
2013-08-01 13:36:15 +02:00
2015-09-07 21:51:13 +02:00
return Response.ok().entity(response.build()).build();
2013-07-27 18:33:20 +02:00
}
2016-05-28 23:09:52 +02:00
/**
* Enable/disable guest login.
*
* @api {post} /app/guest_login Enable/disable guest login
* @apiName PostAppGuestLogin
* @apiGroup App
* @apiParam {Boolean} enabled If true, enable guest login
* @apiError (client) ForbiddenError Access denied
* @apiPermission admin
* @apiVersion 1.5.0
*
* @param enabled If true, enable guest login
* @return Response
*/
@POST
@Path("guest_login")
public Response guestLogin(@FormParam("enabled") Boolean enabled) {
if (!authenticate()) {
throw new ForbiddenClientException();
}
checkBaseFunction(BaseFunction.ADMIN);
ConfigDao configDao = new ConfigDao();
configDao.update(ConfigType.GUEST_LOGIN, enabled.toString());
return Response.ok().build();
}
2013-07-27 18:33:20 +02:00
/**
* Retrieve the application logs.
*
* @api {get} /app/log Get application logs
* @apiName GetAppLog
* @apiGroup App
* @apiParam {String="FATAL","ERROR","WARN","INFO","DEBUG"} level Minimum log level
* @apiParam {String} tag Filter on this logger tag
* @apiParam {String} message Filter on this message
* @apiParam {Number} limit Total number of logs to return
* @apiParam {Number} offset Start at this index
* @apiSuccess {String} total Total number of logs
* @apiSuccess {Object[]} logs List of logs
* @apiSuccess {String} logs.date Date
* @apiSuccess {String} logs.level Level
* @apiSuccess {String} logs.tag Tag
* @apiSuccess {String} logs.message Message
* @apiError (client) ForbiddenError Access denied
* @apiError (server) ServerError MEMORY appender not configured
* @apiPermission user
* @apiVersion 1.5.0
*
* @param minLevel Filter on logging level
2013-07-27 18:33:20 +02:00
* @param tag Filter on logger name / tag
* @param message Filter on message
* @param limit Page limit
* @param offset Page offset
2013-08-13 20:20:28 +02:00
* @return Response
2013-07-27 18:33:20 +02:00
*/
@GET
@Path("log")
public Response log(
@QueryParam("level") String minLevel,
2013-07-27 18:33:20 +02:00
@QueryParam("tag") String tag,
@QueryParam("message") String message,
@QueryParam("limit") Integer limit,
2015-09-07 21:51:13 +02:00
@QueryParam("offset") Integer offset) {
2013-07-27 18:33:20 +02:00
if (!authenticate()) {
throw new ForbiddenClientException();
}
2013-07-27 18:33:20 +02:00
// Get the memory appender
org.apache.log4j.Logger logger = org.apache.log4j.Logger.getRootLogger();
2013-07-27 18:33:20 +02:00
Appender appender = logger.getAppender("MEMORY");
if (appender == null || !(appender instanceof MemoryAppender)) {
throw new ServerException("ServerError", "MEMORY appender not configured");
}
MemoryAppender memoryAppender = (MemoryAppender) appender;
// Find the logs
LogCriteria logCriteria = new LogCriteria()
.setMinLevel(Level.toLevel(StringUtils.stripToNull(minLevel)))
.setTag(StringUtils.stripToNull(tag))
.setMessage(StringUtils.stripToNull(message));
2013-07-27 18:33:20 +02:00
PaginatedList<LogEntry> paginatedList = PaginatedLists.create(limit, offset);
memoryAppender.find(logCriteria, paginatedList);
2015-09-07 21:51:13 +02:00
JsonArrayBuilder logs = Json.createArrayBuilder();
2013-07-27 18:33:20 +02:00
for (LogEntry logEntry : paginatedList.getResultList()) {
2015-09-07 21:51:13 +02:00
logs.add(Json.createObjectBuilder()
.add("date", logEntry.getTimestamp())
.add("level", logEntry.getLevel().toString())
2015-09-07 21:51:13 +02:00
.add("tag", logEntry.getTag())
.add("message", logEntry.getMessage()));
2013-07-27 18:33:20 +02:00
}
2015-09-07 21:51:13 +02:00
JsonObjectBuilder response = Json.createObjectBuilder()
.add("total", paginatedList.getResultCount())
.add("logs", logs);
return Response.ok().entity(response.build()).build();
2013-07-27 18:33:20 +02:00
}
/**
* Destroy and rebuild Lucene index.
*
* @api {post} /app/batch/reindex Rebuild the search index
* @apiName PostAppBatchReindex
* @apiGroup App
* @apiSuccess {String} status Status OK
* @apiError (client) ForbiddenError Access denied
* @apiError (server) IndexingError Error rebuilding the index
* @apiPermission admin
* @apiVersion 1.5.0
*
* @return Response
*/
@POST
@Path("batch/reindex")
2015-09-07 21:51:13 +02:00
public Response batchReindex() {
if (!authenticate()) {
throw new ForbiddenClientException();
}
checkBaseFunction(BaseFunction.ADMIN);
try {
AppContext.getInstance().getIndexingService().rebuildIndex();
} catch (Exception e) {
throw new ServerException("IndexingError", "Error rebuilding the index", e);
}
2015-09-07 21:51:13 +02:00
// Always return OK
JsonObjectBuilder response = Json.createObjectBuilder()
.add("status", "ok");
return Response.ok().entity(response.build()).build();
}
/**
2013-08-20 21:51:07 +02:00
* Clean storage.
*
* @api {post} /app/batch/clean_storage Clean the file and DB storage
* @apiName PostAppBatchCleanStorage
* @apiGroup App
* @apiSuccess {String} status Status OK
* @apiError (client) ForbiddenError Access denied
* @apiError (server) FileError Error deleting orphan files
* @apiPermission admin
* @apiVersion 1.5.0
*
* @return Response
*/
@POST
@Path("batch/clean_storage")
2015-09-07 21:51:13 +02:00
public Response batchCleanStorage() {
if (!authenticate()) {
throw new ForbiddenClientException();
}
checkBaseFunction(BaseFunction.ADMIN);
// Get all files
FileDao fileDao = new FileDao();
List<File> fileList = fileDao.findAll();
Map<String, File> fileMap = new HashMap<>();
for (File file : fileList) {
fileMap.put(file.getId(), file);
}
log.info("Checking {} files", fileMap.size());
// Check if each stored file is valid
try (DirectoryStream<java.nio.file.Path> storedFileList = Files.newDirectoryStream(DirectoryUtil.getStorageDirectory())) {
for (java.nio.file.Path storedFile : storedFileList) {
String fileName = storedFile.getFileName().toString();
String[] fileNameArray = fileName.split("_");
if (!fileMap.containsKey(fileNameArray[0])) {
log.info("Deleting orphan files at this location: {}", storedFile);
Files.delete(storedFile);
}
}
} catch (IOException e) {
throw new ServerException("FileError", "Error deleting orphan files", e);
}
// Hard delete orphan audit logs
EntityManager em = ThreadLocalContext.get().getEntityManager();
StringBuilder sb = new StringBuilder("delete from T_AUDIT_LOG al where al.LOG_ID_C in (select al.LOG_ID_C from T_AUDIT_LOG al ");
sb.append(" left join T_DOCUMENT d on d.DOC_ID_C = al.LOG_IDENTITY_C and d.DOC_DELETEDATE_D is null ");
sb.append(" left join T_ACL a on a.ACL_ID_C = al.LOG_IDENTITY_C and a.ACL_DELETEDATE_D is null ");
sb.append(" left join T_COMMENT c on c.COM_ID_C = al.LOG_IDENTITY_C and c.COM_DELETEDATE_D is null ");
sb.append(" left join T_FILE f on f.FIL_ID_C = al.LOG_IDENTITY_C and f.FIL_DELETEDATE_D is null ");
sb.append(" left join T_TAG t on t.TAG_ID_C = al.LOG_IDENTITY_C and t.TAG_DELETEDATE_D is null ");
sb.append(" left join T_USER u on u.USE_ID_C = al.LOG_IDENTITY_C and u.USE_DELETEDATE_D is null ");
sb.append(" left join T_GROUP g on g.GRP_ID_C = al.LOG_IDENTITY_C and g.GRP_DELETEDATE_D is null ");
sb.append(" where d.DOC_ID_C is null and a.ACL_ID_C is null and c.COM_ID_C is null and f.FIL_ID_C is null and t.TAG_ID_C is null and u.USE_ID_C is null and g.GRP_ID_C is null)");
Query q = em.createNativeQuery(sb.toString());
log.info("Deleting {} orphan audit logs", q.executeUpdate());
2015-12-01 01:16:57 +01:00
// Soft delete orphan ACLs
sb = new StringBuilder("update T_ACL a set ACL_DELETEDATE_D = :dateNow where a.ACL_ID_C in (select a.ACL_ID_C from T_ACL a ");
sb.append(" left join T_SHARE s on s.SHA_ID_C = a.ACL_TARGETID_C ");
sb.append(" left join T_USER u on u.USE_ID_C = a.ACL_TARGETID_C ");
sb.append(" left join T_GROUP g on g.GRP_ID_C = a.ACL_TARGETID_C ");
sb.append(" left join T_DOCUMENT d on d.DOC_ID_C = a.ACL_SOURCEID_C ");
sb.append(" left join T_TAG t on t.TAG_ID_C = a.ACL_SOURCEID_C ");
sb.append(" where s.SHA_ID_C is null and u.USE_ID_C is null and g.GRP_ID_C is null or d.DOC_ID_C is null and t.TAG_ID_C is null)");
q = em.createNativeQuery(sb.toString());
2015-12-01 01:16:57 +01:00
q.setParameter("dateNow", new Date());
log.info("Deleting {} orphan ACLs", q.executeUpdate());
2015-12-01 01:16:57 +01:00
// Soft delete orphan comments
q = em.createNativeQuery("update T_COMMENT c set c.COM_DELETEDATE_D = :dateNow where c.COM_ID_C in (select c.COM_ID_C from T_COMMENT c left join T_DOCUMENT d on d.DOC_ID_C = c.COM_IDDOC_C and d.DOC_DELETEDATE_D is null where d.DOC_ID_C is null)");
q.setParameter("dateNow", new Date());
log.info("Deleting {} orphan comments", q.executeUpdate());
2015-12-01 01:16:57 +01:00
// Soft delete orphan document tag links
q = em.createNativeQuery("update T_DOCUMENT_TAG dt set dt.DOT_DELETEDATE_D = :dateNow where dt.DOT_ID_C in (select dt.DOT_ID_C from T_DOCUMENT_TAG dt left join T_DOCUMENT d on dt.DOT_IDDOCUMENT_C = d.DOC_ID_C and d.DOC_DELETEDATE_D is null left join T_TAG t on t.TAG_ID_C = dt.DOT_IDTAG_C and t.TAG_DELETEDATE_D is null where d.DOC_ID_C is null or t.TAG_ID_C is null)");
q.setParameter("dateNow", new Date());
log.info("Deleting {} orphan document tag links", q.executeUpdate());
2015-12-01 01:16:57 +01:00
// Soft delete orphan shares
q = em.createNativeQuery("update T_SHARE s set s.SHA_DELETEDATE_D = :dateNow where s.SHA_ID_C in (select s.SHA_ID_C from T_SHARE s left join T_ACL a on a.ACL_TARGETID_C = s.SHA_ID_C and a.ACL_DELETEDATE_D is null where a.ACL_ID_C is null)");
q.setParameter("dateNow", new Date());
log.info("Deleting {} orphan shares", q.executeUpdate());
2015-12-01 01:16:57 +01:00
// Soft delete orphan tags
q = em.createNativeQuery("update T_TAG t set t.TAG_DELETEDATE_D = :dateNow where t.TAG_ID_C in (select t.TAG_ID_C from T_TAG t left join T_USER u on u.USE_ID_C = t.TAG_IDUSER_C and u.USE_DELETEDATE_D is null where u.USE_ID_C is null)");
q.setParameter("dateNow", new Date());
log.info("Deleting {} orphan tags", q.executeUpdate());
2015-12-01 01:16:57 +01:00
// Soft delete orphan documents
q = em.createNativeQuery("update T_DOCUMENT d set d.DOC_DELETEDATE_D = :dateNow where d.DOC_ID_C in (select d.DOC_ID_C from T_DOCUMENT d left join T_USER u on u.USE_ID_C = d.DOC_IDUSER_C and u.USE_DELETEDATE_D is null where u.USE_ID_C is null)");
q.setParameter("dateNow", new Date());
log.info("Deleting {} orphan documents", q.executeUpdate());
// Soft delete orphan files
q = em.createNativeQuery("update T_FILE f set f.FIL_DELETEDATE_D = :dateNow where f.FIL_ID_C in (select f.FIL_ID_C from T_FILE f left join T_USER u on u.USE_ID_C = f.FIL_IDUSER_C and u.USE_DELETEDATE_D is null where u.USE_ID_C is null)");
q.setParameter("dateNow", new Date());
log.info("Deleting {} orphan files", q.executeUpdate());
// Hard delete softly deleted data
log.info("Deleting {} soft deleted document tag links", em.createQuery("delete DocumentTag dt where dt.deleteDate is not null").executeUpdate());
log.info("Deleting {} soft deleted ACLs", em.createQuery("delete Acl a where a.deleteDate is not null").executeUpdate());
log.info("Deleting {} soft deleted shares", em.createQuery("delete Share s where s.deleteDate is not null").executeUpdate());
log.info("Deleting {} soft deleted tags", em.createQuery("delete Tag t where t.deleteDate is not null").executeUpdate());
log.info("Deleting {} soft deleted comments", em.createQuery("delete Comment c where c.deleteDate is not null").executeUpdate());
log.info("Deleting {} soft deleted files", em.createQuery("delete File f where f.deleteDate is not null").executeUpdate());
log.info("Deleting {} soft deleted documents", em.createQuery("delete Document d where d.deleteDate is not null").executeUpdate());
log.info("Deleting {} soft deleted users", em.createQuery("delete User u where u.deleteDate is not null").executeUpdate());
log.info("Deleting {} soft deleted groups", em.createQuery("delete Group g where g.deleteDate is not null").executeUpdate());
2015-09-07 21:51:13 +02:00
// Always return OK
JsonObjectBuilder response = Json.createObjectBuilder()
.add("status", "ok");
return Response.ok().entity(response.build()).build();
}
/**
* Recompute the quota for each user.
*
2016-05-28 23:09:52 +02:00
* @api {post} /app/batch/recompute_quota Recompute user quotas
* @apiName PostAppBatchRecomputeQuota
* @apiGroup App
* @apiSuccess {String} status Status OK
* @apiError (client) ForbiddenError Access denied
* @apiError (server) MissingFile File does not exist
* @apiPermission admin
* @apiVersion 1.5.0
*
* @return Response
*/
@POST
@Path("batch/recompute_quota")
public Response batchRecomputeQuota() {
if (!authenticate()) {
throw new ForbiddenClientException();
}
checkBaseFunction(BaseFunction.ADMIN);
// Get all files
FileDao fileDao = new FileDao();
List<File> fileList = fileDao.findAll();
// Count each file for the corresponding user quota
UserDao userDao = new UserDao();
Map<String, User> userMap = new HashMap<>();
for (File file : fileList) {
java.nio.file.Path storedFile = DirectoryUtil.getStorageDirectory().resolve(file.getId());
User user;
if (userMap.containsKey(file.getUserId())) {
user = userMap.get(file.getUserId());
} else {
user = userDao.getById(file.getUserId());
user.setStorageCurrent(0L);
userMap.put(user.getId(), user);
}
try {
user.setStorageCurrent(user.getStorageCurrent() + Files.size(storedFile));
} catch (IOException e) {
throw new ServerException("MissingFile", "File does not exist", e);
}
}
// Save all users
for (User user : userMap.values()) {
if (user.getDeleteDate() == null) {
userDao.updateQuota(user);
}
}
// Always return OK
JsonObjectBuilder response = Json.createObjectBuilder()
.add("status", "ok");
return Response.ok().entity(response.build()).build();
}
/**
* Add base ACLs to tags.
*
2016-05-28 23:09:52 +02:00
* @api {post} /app/batch/tag_acls Add base ACL to tags
* @apiDescription This resource must be used after migrating to 1.5.
* It will not do anything if base ACL are already present on tags.
* @apiName PostAppBatchTagAcls
* @apiGroup App
* @apiSuccess {String} status Status OK
* @apiError (client) ForbiddenError Access denied
* @apiPermission admin
* @apiVersion 1.5.0
*
* @return Response
*/
@POST
@Path("batch/tag_acls")
public Response batchTagAcls() {
if (!authenticate()) {
throw new ForbiddenClientException();
}
checkBaseFunction(BaseFunction.ADMIN);
// Get all tags
TagDao tagDao = new TagDao();
2016-05-08 23:20:58 +02:00
UserDao userDao = new UserDao();
List<TagDto> tagDtoList = tagDao.findByCriteria(new TagCriteria(), null);
// Add READ and WRITE ACLs
for (TagDto tagDto : tagDtoList) {
2016-05-08 23:20:58 +02:00
// Remove old ACLs
AclDao aclDao = new AclDao();
List<AclDto> aclDtoList = aclDao.getBySourceId(tagDto.getId());
2016-05-08 23:20:58 +02:00
String userId = userDao.getActiveByUsername(tagDto.getCreator()).getId();
if (aclDtoList.size() == 0) {
// Create read ACL
Acl acl = new Acl();
acl.setPerm(PermType.READ);
acl.setSourceId(tagDto.getId());
acl.setTargetId(userId);
aclDao.create(acl, userId);
// Create write ACL
acl = new Acl();
acl.setPerm(PermType.WRITE);
acl.setSourceId(tagDto.getId());
acl.setTargetId(userId);
aclDao.create(acl, userId);
}
}
// Always return OK
JsonObjectBuilder response = Json.createObjectBuilder()
.add("status", "ok");
return Response.ok().entity(response.build()).build();
}
2013-07-27 18:33:20 +02:00
}