From 4e768e9103d6e74bc69c1a94b5f7cfd3a4704a9d Mon Sep 17 00:00:00 2001 From: jendib Date: Mon, 18 Apr 2016 00:00:46 +0200 Subject: [PATCH] #79: POST /theme/color to change the main color --- .../docs/core/constant/ConfigType.java | 4 + .../sismics/docs/core/dao/jpa/ConfigDao.java | 19 +++++ .../docs/core/dao/jpa/ContributorDao.java | 4 +- .../sismics/docs/core/dao/jpa/FileDao.java | 3 +- .../com/sismics/docs/core/dao/jpa/TagDao.java | 15 ++-- .../util/context/ThreadLocalContext.java | 11 +-- .../com/sismics/rest/util/ValidationUtil.java | 3 +- .../docs/rest/resource/BaseResource.java | 8 +- .../docs/rest/resource/TagResource.java | 1 - .../docs/rest/resource/ThemeResource.java | 84 ++++++++++++++++++- .../sismics/docs/rest/TestThemeResource.java | 22 ++++- 11 files changed, 138 insertions(+), 36 deletions(-) diff --git a/docs-core/src/main/java/com/sismics/docs/core/constant/ConfigType.java b/docs-core/src/main/java/com/sismics/docs/core/constant/ConfigType.java index 736b5ee3..e5da8cc6 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/constant/ConfigType.java +++ b/docs-core/src/main/java/com/sismics/docs/core/constant/ConfigType.java @@ -10,4 +10,8 @@ public enum ConfigType { * Lucene directory storage type. */ LUCENE_DIRECTORY_STORAGE, + /** + * Theme configuration. + */ + THEME } diff --git a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/ConfigDao.java b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/ConfigDao.java index 50bbc203..a0d948f2 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/ConfigDao.java +++ b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/ConfigDao.java @@ -33,4 +33,23 @@ public class ConfigDao { return null; } } + + /** + * Updates a configuration parameter. + * + * @param id Configuration parameter ID + * @param value Configuration parameter value + */ + public void update(ConfigType id, String value) { + EntityManager em = ThreadLocalContext.get().getEntityManager(); + Config config = getById(id); + if (config == null) { + config = new Config(); + config.setId(id); + config.setValue(value); + em.persist(config); + } else { + config.setValue(value); + } + } } diff --git a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/ContributorDao.java b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/ContributorDao.java index d9d465bc..0b21acb6 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/ContributorDao.java +++ b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/ContributorDao.java @@ -21,9 +21,7 @@ public class ContributorDao { * Creates a new contributor. * * @param contributor Contributor - * @param userId User ID * @return New ID - * @throws Exception */ public String create(Contributor contributor) { // Create the UUID @@ -72,7 +70,7 @@ public class ContributorDao { int i = 0; ContributorDto contributorDto = new ContributorDto(); contributorDto.setUsername((String) o[i++]); - contributorDto.setEmail((String) o[i++]); + contributorDto.setEmail((String) o[i]); contributorDtoList.add(contributorDto); } return contributorDtoList; diff --git a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/FileDao.java b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/FileDao.java index ed2036f8..8d080037 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/FileDao.java +++ b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/FileDao.java @@ -25,7 +25,6 @@ public class FileDao { * @param file File * @param userId User ID * @return New ID - * @throws Exception */ public String create(File file, String userId) { // Create the UUID @@ -169,7 +168,7 @@ public class FileDao { /** * Get files by document ID or all orphan files of an user. * - * @parma userId User ID + * @param userId User ID * @param documentId Document ID * @return List of files */ diff --git a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/TagDao.java b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/TagDao.java index 9f0a8f4f..66f3e20d 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/TagDao.java +++ b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/TagDao.java @@ -108,7 +108,7 @@ public class TagDao { sb.append(" where dt.DOT_IDDOCUMENT_C = :documentId and t.TAG_DELETEDATE_D is null "); sb.append(" and t.TAG_IDUSER_C = :userId and dt.DOT_DELETEDATE_D is null "); sb.append(" order by t.TAG_NAME_C "); - + // Perform the query Query q = em.createNativeQuery(sb.toString()); q.setParameter("documentId", documentId); @@ -116,14 +116,14 @@ public class TagDao { List l = q.getResultList(); // Assemble results - List tagDtoList = new ArrayList(); + List tagDtoList = new ArrayList<>(); for (Object[] o : l) { int i = 0; TagDto tagDto = new TagDto(); tagDto.setId((String) o[i++]); tagDto.setName((String) o[i++]); tagDto.setColor((String) o[i++]); - tagDto.setParentId((String) o[i++]); + tagDto.setParentId((String) o[i]); tagDtoList.add(tagDto); } return tagDtoList; @@ -132,7 +132,7 @@ public class TagDao { /** * Returns stats on tags. * - * @param documentId Document ID + * @param userId User ID * @return Stats by tag */ @SuppressWarnings("unchecked") @@ -145,14 +145,14 @@ public class TagDao { sb.append(" where t.TAG_IDUSER_C = :userId and t.TAG_DELETEDATE_D is null "); sb.append(" group by t.TAG_ID_C "); sb.append(" order by t.TAG_NAME_C "); - + // Perform the query Query q = em.createNativeQuery(sb.toString()); q.setParameter("userId", userId); List l = q.getResultList(); // Assemble results - List tagStatDtoList = new ArrayList(); + List tagStatDtoList = new ArrayList<>(); for (Object[] o : l) { int i = 0; TagStatDto tagDto = new TagStatDto(); @@ -160,7 +160,7 @@ public class TagDao { tagDto.setName((String) o[i++]); tagDto.setColor((String) o[i++]); tagDto.setParentId((String) o[i++]); - tagDto.setCount(((Number) o[i++]).intValue()); + tagDto.setCount(((Number) o[i]).intValue()); tagStatDtoList.add(tagDto); } return tagStatDtoList; @@ -172,7 +172,6 @@ public class TagDao { * @param tag Tag * @param userId User ID * @return New ID - * @throws Exception */ public String create(Tag tag, String userId) { // Create the UUID 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 bff923c3..71ff24f1 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 @@ -11,7 +11,7 @@ public class ThreadLocalContext { /** * ThreadLocal to store the context. */ - public static final ThreadLocal threadLocalContext = new ThreadLocal(); + private static final ThreadLocal threadLocalContext = new ThreadLocal<>(); /** * Entity manager. @@ -46,15 +46,6 @@ public class ThreadLocalContext { threadLocalContext.set(null); } - /** - * Returns true only if the entity manager is defined. - * - * @return Condition - */ - public boolean isInTransactionalContext() { - return entityManager != null; - } - /** * Getter of entityManager. * diff --git a/docs-web-common/src/main/java/com/sismics/rest/util/ValidationUtil.java b/docs-web-common/src/main/java/com/sismics/rest/util/ValidationUtil.java index ef962fb1..a9ecfb8e 100644 --- a/docs-web-common/src/main/java/com/sismics/rest/util/ValidationUtil.java +++ b/docs-web-common/src/main/java/com/sismics/rest/util/ValidationUtil.java @@ -108,10 +108,9 @@ public class ValidationUtil { * @param s String to validate * @param name Name of the parameter * @param nullable True if the string can be empty or null - * @throws JSONException */ public static void validateHexColor(String s, String name, boolean nullable) throws ClientException { - ValidationUtil.validateLength(s, "name", 7, 7, nullable); + ValidationUtil.validateLength(s, name, 7, 7, nullable); } /** diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/BaseResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/BaseResource.java index f7e607a0..ad3fe187 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/BaseResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/BaseResource.java @@ -57,9 +57,8 @@ public abstract class BaseResource { * Checks if the user has a base function. Throw an exception if the check fails. * * @param baseFunction Base function to check - * @throws JSONException */ - protected void checkBaseFunction(BaseFunction baseFunction) { + void checkBaseFunction(BaseFunction baseFunction) { if (!hasBaseFunction(baseFunction)) { throw new ForbiddenClientException(); } @@ -70,9 +69,8 @@ public abstract class BaseResource { * * @param baseFunction Base function to check * @return True if the user has the base function - * @throws JSONException */ - protected boolean hasBaseFunction(BaseFunction baseFunction) { + boolean hasBaseFunction(BaseFunction baseFunction) { if (principal == null || !(principal instanceof UserPrincipal)) { return false; } @@ -86,7 +84,7 @@ public abstract class BaseResource { * @param shareId Share ID (optional) * @return List of ACL target ID */ - protected List getTargetIdList(String shareId) { + List getTargetIdList(String shareId) { List targetIdList = Lists.newArrayList(principal.getGroupIdSet()); if (principal.getId() != null) { targetIdList.add(principal.getId()); diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/TagResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/TagResource.java index 8caf253e..97b8e4d4 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/TagResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/TagResource.java @@ -64,7 +64,6 @@ public class TagResource extends BaseResource { * Returns stats on tags. * * @return Response - * @throws JSONException */ @GET @Path("/stats") diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/ThemeResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/ThemeResource.java index ec1f32a0..ea38810e 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/ThemeResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/ThemeResource.java @@ -1,12 +1,21 @@ package com.sismics.docs.rest.resource; -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; +import javax.json.*; +import javax.ws.rs.*; import javax.ws.rs.core.Response; +import com.google.common.base.Strings; +import com.sismics.docs.core.constant.ConfigType; +import com.sismics.docs.core.dao.jpa.ConfigDao; +import com.sismics.docs.core.model.jpa.Config; +import com.sismics.docs.rest.constant.BaseFunction; +import com.sismics.rest.exception.ForbiddenClientException; +import com.sismics.rest.util.ValidationUtil; import com.sismics.util.css.Selector; +import java.io.StringReader; +import java.util.Map; + /** * Theme REST resources. * @@ -23,8 +32,75 @@ public class ThemeResource extends BaseResource { @Path("/stylesheet") @Produces("text/css") public Response stylesheet() { + JsonObject themeConfig = getThemeConfig(); + + // Build the stylesheet StringBuilder sb = new StringBuilder(); - sb.append(new Selector("body")); + sb.append(new Selector(".navbar") + .rule("background-color", themeConfig.getString("color", "inherit"))); + return Response.ok().entity(sb.toString()).build(); } + + @POST + @Path("/color") + public Response color(@FormParam("color") String color) { + if (!authenticate()) { + throw new ForbiddenClientException(); + } + checkBaseFunction(BaseFunction.ADMIN); + + // Validate input data + ValidationUtil.validateHexColor(color, "color", true); + + // Update the JSON + JsonObjectBuilder json = getMutableThemeConfig(); + if (Strings.isNullOrEmpty(color)) { + json.add("color", JsonValue.NULL); + } else { + json.add("color", color); + } + + // Persist the new configuration + ConfigDao configDao = new ConfigDao(); + configDao.update(ConfigType.THEME, json.build().toString()); + + // Always return OK + JsonObjectBuilder response = Json.createObjectBuilder() + .add("status", "ok"); + return Response.ok().entity(response.build()).build(); + } + + /** + * Returns the theme configuration object. + * + * @return Theme configuration + */ + private JsonObject getThemeConfig() { + ConfigDao configDao = new ConfigDao(); + Config themeConfig = configDao.getById(ConfigType.THEME); + if (themeConfig == null) { + return Json.createObjectBuilder().build(); + } + + try (JsonReader reader = Json.createReader(new StringReader(themeConfig.getValue()))) { + return reader.readObject(); + } + } + + /** + * Returns a mutable theme configuration. + * + * @return Json builder + */ + private JsonObjectBuilder getMutableThemeConfig() { + JsonObject themeConfig = getThemeConfig(); + JsonObjectBuilder json = Json.createObjectBuilder(); + + for (Map.Entry entry : themeConfig.entrySet()) { + json.add(entry.getKey(), entry.getValue()); + } + + return json; + } } diff --git a/docs-web/src/test/java/com/sismics/docs/rest/TestThemeResource.java b/docs-web/src/test/java/com/sismics/docs/rest/TestThemeResource.java index 461a196e..2de11477 100644 --- a/docs-web/src/test/java/com/sismics/docs/rest/TestThemeResource.java +++ b/docs-web/src/test/java/com/sismics/docs/rest/TestThemeResource.java @@ -1,7 +1,13 @@ package com.sismics.docs.rest; +import com.sismics.util.filter.TokenBasedSecurityFilter; +import org.junit.Assert; import org.junit.Test; +import javax.json.JsonObject; +import javax.ws.rs.client.Entity; +import javax.ws.rs.core.Form; + /** * Test the theme resource. * @@ -13,9 +19,23 @@ public class TestThemeResource extends BaseJerseyTest { */ @Test public void testThemeResource() { + // Login admin + String adminToken = clientUtil.login("admin", "admin", false); + // Get the stylesheet anonymously String stylesheet = target().path("/theme/stylesheet").request() .get(String.class); - System.out.println(stylesheet); + Assert.assertTrue(stylesheet.contains("background-color: inherit;")); + + // Update the main color as admin + target().path("/theme/color").request() + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken) + .post(Entity.form(new Form() + .param("color", "#ff0000")), JsonObject.class); + + // Get the stylesheet anonymously + stylesheet = target().path("/theme/stylesheet").request() + .get(String.class); + Assert.assertTrue(stylesheet.contains("background-color: #ff0000;")); } } \ No newline at end of file