From: Jean-Baptiste Lievremont Date: Wed, 4 Jun 2014 09:51:43 +0000 (+0200) Subject: SONAR-5334 Implement l10n cache control mechanism on server side X-Git-Tag: 4.4-RC1~641 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=373adc53629499d53843c6e326293b685cf3fea9;p=sonarqube.git SONAR-5334 Implement l10n cache control mechanism on server side --- diff --git a/sonar-server/src/main/java/org/sonar/server/platform/ws/L10nWs.java b/sonar-server/src/main/java/org/sonar/server/platform/ws/L10nWs.java index f267b4acfc6..5d75175186a 100644 --- a/sonar-server/src/main/java/org/sonar/server/platform/ws/L10nWs.java +++ b/sonar-server/src/main/java/org/sonar/server/platform/ws/L10nWs.java @@ -20,6 +20,7 @@ package org.sonar.server.platform.ws; import org.apache.commons.lang.LocaleUtils; +import org.sonar.api.platform.Server; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.RequestHandler; import org.sonar.api.server.ws.Response; @@ -28,45 +29,61 @@ import org.sonar.api.utils.text.JsonWriter; import org.sonar.core.i18n.DefaultI18n; import org.sonar.server.user.UserSession; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.util.Date; import java.util.Locale; public class L10nWs implements WebService { private final DefaultI18n i18n; + private final Server server; - public L10nWs(DefaultI18n i18n) { + public L10nWs(DefaultI18n i18n, Server server) { this.i18n = i18n; + this.server = server; } @Override public void define(Context context) { NewController l10n = context.createController("api/l10n"); l10n.setDescription("Localization") - .setSince("4.4") - .createAction("index") - .setInternal(true) - .setDescription("Get all localization messages for a given locale") - .setHandler(new RequestHandler() { - @Override - public void handle(Request request, Response response) throws Exception { - serializeMessages(request, response); - } - }).createParam("locale") - .setDescription("BCP47 language tag, used to override the browser Accept-Language header") - .setExampleValue("fr-CH"); + .setSince("4.4"); + NewAction indexAction = l10n.createAction("index") + .setInternal(true) + .setDescription("Get all localization messages for a given locale") + .setHandler(new RequestHandler() { + @Override + public void handle(Request request, Response response) throws Exception { + serializeMessages(request, response); + } + }); + indexAction.createParam("locale") + .setDescription("BCP47 language tag, used to override the browser Accept-Language header") + .setExampleValue("fr-CH"); + indexAction.createParam("ts") + .setDescription("UTC timestamp of the last cache update") + .setExampleValue("2014-06-04T09:31:42Z"); + l10n.done(); } - protected void serializeMessages(Request request, Response response) { - Locale locale = UserSession.get().locale(); - String localeParam = request.param("locale"); - if (localeParam != null) { - locale = LocaleUtils.toLocale(localeParam); - } - JsonWriter json = response.newJsonWriter().beginObject(); - for (String messageKey: i18n.getPropertyKeys()) { - json.prop(messageKey, i18n.message(locale, messageKey, messageKey)); + protected void serializeMessages(Request request, Response response) throws IOException { + Date timestamp = request.paramAsDateTime("ts"); + if (timestamp != null && timestamp.after(server.getStartedAt())) { + response.stream().setStatus(HttpURLConnection.HTTP_NOT_MODIFIED).output().close(); + } else { + + Locale locale = UserSession.get().locale(); + String localeParam = request.param("locale"); + if (localeParam != null) { + locale = LocaleUtils.toLocale(localeParam); + } + JsonWriter json = response.newJsonWriter().beginObject(); + for (String messageKey: i18n.getPropertyKeys()) { + json.prop(messageKey, i18n.message(locale, messageKey, messageKey)); + } + json.endObject().close(); } - json.endObject().close(); } } diff --git a/sonar-server/src/test/java/org/sonar/server/platform/ws/L10nWsTest.java b/sonar-server/src/test/java/org/sonar/server/platform/ws/L10nWsTest.java index 219405875f2..810f325b827 100644 --- a/sonar-server/src/test/java/org/sonar/server/platform/ws/L10nWsTest.java +++ b/sonar-server/src/test/java/org/sonar/server/platform/ws/L10nWsTest.java @@ -24,14 +24,18 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; +import org.sonar.api.platform.Server; +import org.sonar.api.utils.DateUtils; import org.sonar.core.i18n.DefaultI18n; import org.sonar.server.user.MockUserSession; import org.sonar.server.ws.WsTester; import org.sonar.server.ws.WsTester.Result; +import java.util.Date; import java.util.Locale; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) @@ -40,11 +44,34 @@ public class L10nWsTest { @Mock DefaultI18n i18n; + @Mock + Server server; + @Test - public void should_return_all_l10n_messages_using_accept_header() throws Exception { + public void should_allow_client_to_cache_messages() throws Exception { Locale locale = Locale.PRC; MockUserSession.set().setLocale(locale); + Date now = new Date(); + Date aBitLater = new Date(now.getTime() + 1000); + when(server.getStartedAt()).thenReturn(now); + + Result result = new WsTester(new L10nWs(i18n, server)).newGetRequest("api/l10n", "index").setParam("ts", DateUtils.formatDateTime(aBitLater)).execute(); + verifyZeroInteractions(i18n); + verify(server).getStartedAt(); + + result.assertNotModified(); + } + + @Test + public void should_return_all_l10n_messages_using_accept_header_with_cache_expired() throws Exception { + Locale locale = Locale.PRC; + MockUserSession.set().setLocale(locale); + + Date now = new Date(); + Date aBitEarlier = new Date(now.getTime() - 1000); + when(server.getStartedAt()).thenReturn(now); + String key1 = "key1"; String key2 = "key2"; String key3 = "key3"; @@ -54,7 +81,7 @@ public class L10nWsTest { when(i18n.message(locale, key2, key2)).thenReturn(key2); when(i18n.message(locale, key3, key3)).thenReturn(key3); - Result result = new WsTester(new L10nWs(i18n)).newGetRequest("api/l10n", "index").execute(); + Result result = new WsTester(new L10nWs(i18n, server)).newGetRequest("api/l10n", "index").setParam("ts", DateUtils.formatDateTime(aBitEarlier)).execute(); verify(i18n).getPropertyKeys(); verify(i18n).message(locale, key1, key1); verify(i18n).message(locale, key2, key2); @@ -78,7 +105,7 @@ public class L10nWsTest { when(i18n.message(override, key2, key2)).thenReturn(key2); when(i18n.message(override, key3, key3)).thenReturn(key3); - Result result = new WsTester(new L10nWs(i18n)).newGetRequest("api/l10n", "index").setParam("locale", override.toString()).execute(); + Result result = new WsTester(new L10nWs(i18n, server)).newGetRequest("api/l10n", "index").setParam("locale", override.toString()).execute(); verify(i18n).getPropertyKeys(); verify(i18n).message(override, key1, key1); verify(i18n).message(override, key2, key2); diff --git a/sonar-server/src/test/java/org/sonar/server/qualitygate/ws/QGatesWsTest.java b/sonar-server/src/test/java/org/sonar/server/qualitygate/ws/QGatesWsTest.java index e57f5ebfaed..78adc0d479e 100644 --- a/sonar-server/src/test/java/org/sonar/server/qualitygate/ws/QGatesWsTest.java +++ b/sonar-server/src/test/java/org/sonar/server/qualitygate/ws/QGatesWsTest.java @@ -217,8 +217,8 @@ public class QGatesWsTest { Long id = 42L; String name = "New QG"; when(qGates.rename(id, name)).thenReturn(new QualityGateDto().setId(id).setName(name)); - tester.newGetRequest("api/qualitygates", "rename").setParam("id", id.toString()).setParam("name", name).execute() - .assertNoContent(); + tester.newPostRequest("api/qualitygates", "rename").setParam("id", id.toString()).setParam("name", name).execute() + .assertJson("{'id':42,'name':'New QG'}");; } @Test diff --git a/sonar-server/src/test/java/org/sonar/server/ws/WsTester.java b/sonar-server/src/test/java/org/sonar/server/ws/WsTester.java index 70731234152..bae37674313 100644 --- a/sonar-server/src/test/java/org/sonar/server/ws/WsTester.java +++ b/sonar-server/src/test/java/org/sonar/server/ws/WsTester.java @@ -28,15 +28,20 @@ import org.sonar.api.server.ws.WebService; import org.sonar.api.server.ws.internal.ValidatingRequest; import org.sonar.api.utils.text.JsonWriter; import org.sonar.api.utils.text.XmlWriter; +import org.sonar.server.ws.WsTester.TestResponse.TestStream; import javax.annotation.CheckForNull; import javax.annotation.Nullable; + import java.io.ByteArrayOutputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; +import java.net.HttpURLConnection; import java.net.URL; import java.util.Map; +import static org.fest.assertions.Assertions.assertThat; + /** * @since 4.2 */ @@ -82,6 +87,8 @@ public class WsTester { public static class TestResponse implements Response { + private TestStream stream; + public class TestStream implements Response.Stream { private String mediaType; private int status; @@ -127,12 +134,16 @@ public class WsTester { @Override public Stream stream() { - return new TestStream(); + if (stream == null) { + stream = new TestStream(); + } + return stream; } @Override public Response noContent() { + stream().setStatus(HttpURLConnection.HTTP_NO_CONTENT); IOUtils.closeQuietly(output); return this; } @@ -147,8 +158,7 @@ public class WsTester { } public Result assertNoContent() { - //FIXME - return this; + return assertStatus(HttpURLConnection.HTTP_NO_CONTENT); } public String outputAsString() { @@ -183,6 +193,16 @@ public class WsTester { JSONAssert.assertEquals(IOUtils.toString(url), json, strict); return this; } + + public Result assertNotModified() { + return assertStatus(HttpURLConnection.HTTP_NOT_MODIFIED); + } + + public Result assertStatus(int httpStatus) { + assertThat(((TestStream) response.stream()).status()).isEqualTo(httpStatus); + return this; + } + } private final WebService.Context context = new WebService.Context();