public class HttpRequestUidModule extends Module {
@Override
protected void configureModule() {
- add(RequestUidGeneratorBaseImpl.class,
+ add(new RequestIdConfiguration(RequestUidGeneratorImpl.UUID_GENERATOR_RENEWAL_COUNT),
+ RequestUidGeneratorBaseImpl.class,
RequestUidGeneratorImpl.class);
}
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.platform.web.requestid;
+
+public class RequestIdConfiguration {
+ /**
+ * @see RequestUidGeneratorImpl#mustRenewUuidGenerator(long)
+ */
+ private final long uuidGeneratorRenewalCount;
+
+ public RequestIdConfiguration(long uuidGeneratorRenewalCount) {
+ this.uuidGeneratorRenewalCount = uuidGeneratorRenewalCount;
+ }
+
+ public long getUidGeneratorRenewalCount() {
+ return uuidGeneratorRenewalCount;
+ }
+}
import org.sonar.core.util.UuidGenerator;
-/**
- * Marker interface to be able to add a {@link org.sonar.core.util.UuidGenerator.WithFixedBase} to pico without risk
- * of use in another domain.
- */
-public interface RequestUidGeneratorBase extends UuidGenerator.WithFixedBase {
+public interface RequestUidGeneratorBase {
+ /**
+ * Provides a new instance of {@link UuidGenerator.WithFixedBase} to be used by {@link RequestUidGeneratorImpl}.
+ */
+ UuidGenerator.WithFixedBase createNew();
}
import org.sonar.core.util.UuidGeneratorImpl;
public class RequestUidGeneratorBaseImpl implements RequestUidGeneratorBase {
- private final UuidGenerator.WithFixedBase delegate = new UuidGeneratorImpl().withFixedBase();
@Override
- public byte[] generate(int increment) {
- return delegate.generate(increment);
+ public UuidGenerator.WithFixedBase createNew() {
+ return new UuidGeneratorImpl().withFixedBase();
}
}
package org.sonar.server.platform.web.requestid;
import java.util.Base64;
-import java.util.concurrent.atomic.AtomicInteger;
-import org.sonar.core.util.UuidGeneratorImpl;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+import org.sonar.core.util.UuidGenerator;
/**
* This implementation of {@link RequestUidGenerator} creates unique identifiers for HTTP requests leveraging
- * {@link UuidGeneratorImpl#withFixedBase()}.
+ * {@link UuidGenerator.WithFixedBase#generate(int)} and a counter of HTTP requests.
+ * <p>
+ * To work around the limit of unique values produced by {@link UuidGenerator.WithFixedBase#generate(int)}, the
+ * {@link UuidGenerator.WithFixedBase} instance will be renewed every
+ * {@link RequestIdConfiguration#getUidGeneratorRenewalCount() RequestIdConfiguration#uidGeneratorRenewalCount}
+ * HTTP requests.
+ * </p>
* <p>
* This implementation is Thread safe.
* </p>
*/
public class RequestUidGeneratorImpl implements RequestUidGenerator {
- private final AtomicInteger counter = new AtomicInteger();
+ /**
+ * The value to which the HTTP request count will be compared to (using a modulo operator,
+ * see {@link #mustRenewUuidGenerator(long)}).
+ *
+ * <p>
+ * This value can't be the last value before {@link UuidGenerator.WithFixedBase#generate(int)} returns a non unique
+ * value, ie. 2^23-1 because there is no guarantee the renewal will happen before any other thread calls
+ * {@link UuidGenerator.WithFixedBase#generate(int)} method of the deplated {@link UuidGenerator.WithFixedBase} instance.
+ * </p>
+ *
+ * <p>
+ * To keep a comfortable margin of error, 2^22 will be used.
+ * </p>
+ */
+ public static final long UUID_GENERATOR_RENEWAL_COUNT = 4_194_304;
+
+ private final AtomicLong counter = new AtomicLong();
private final RequestUidGeneratorBase requestUidGeneratorBase;
+ private final RequestIdConfiguration requestIdConfiguration;
+ private final AtomicReference<UuidGenerator.WithFixedBase> uuidGenerator;
- public RequestUidGeneratorImpl(RequestUidGeneratorBase requestUidGeneratorBase) {
+ public RequestUidGeneratorImpl(RequestUidGeneratorBase requestUidGeneratorBase, RequestIdConfiguration requestIdConfiguration) {
this.requestUidGeneratorBase = requestUidGeneratorBase;
+ this.uuidGenerator = new AtomicReference<>(requestUidGeneratorBase.createNew());
+ this.requestIdConfiguration = requestIdConfiguration;
}
@Override
public String generate() {
- return Base64.getEncoder().encodeToString(requestUidGeneratorBase.generate(counter.incrementAndGet()));
+ UuidGenerator.WithFixedBase currentUuidGenerator = this.uuidGenerator.get();
+ long counterValue = counter.getAndIncrement();
+ if (counterValue != 0 && mustRenewUuidGenerator(counterValue)) {
+ UuidGenerator.WithFixedBase newUuidGenerator = requestUidGeneratorBase.createNew();
+ uuidGenerator.set(newUuidGenerator);
+ return generate(newUuidGenerator, counterValue);
+ }
+ return generate(currentUuidGenerator, counterValue);
+ }
+
+ /**
+ * Since renewal of {@link UuidGenerator.WithFixedBase} instance is based on the HTTP request counter, only a single
+ * thread can get the right value which will make this method return true. So, this is thread-safe by design, therefor
+ * this method doesn't need external synchronization.
+ * <p>
+ * The value to which the counter is compared should however be chosen with caution: see {@link #UUID_GENERATOR_RENEWAL_COUNT}.
+ * </p>
+ */
+ private boolean mustRenewUuidGenerator(long counter) {
+ return counter % requestIdConfiguration.getUidGeneratorRenewalCount() == 0;
+ }
+
+ private static String generate(UuidGenerator.WithFixedBase uuidGenerator, long increment) {
+ return Base64.getEncoder().encodeToString(uuidGenerator.generate((int) increment));
}
}
underTest.configure(container);
assertThat(container.getPicoContainer().getComponentAdapters())
- .hasSize(COMPONENTS_HARDCODED_IN_CONTAINER + 2);
+ .hasSize(COMPONENTS_HARDCODED_IN_CONTAINER + 3);
}
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.platform.web.requestid;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class RequestIdConfigurationTest {
+ private RequestIdConfiguration underTest = new RequestIdConfiguration(50);
+
+ @Test
+ public void getUidGeneratorRenewalCount_returns_value_provided_from_constructor() {
+ assertThat(underTest.getUidGeneratorRenewalCount()).isEqualTo(50);
+ }
+}
*/
package org.sonar.server.platform.web.requestid;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.core.util.UuidGenerator;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
public class RequestUidGeneratorImplTest {
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private UuidGenerator.WithFixedBase generator1 = increment -> new byte[] {124, 22, 66, 96, 55, 88, 2, 9};
+ private UuidGenerator.WithFixedBase generator2 = increment -> new byte[] {0, 5, 88, 81, 8, 6, 44, 19};
+ private UuidGenerator.WithFixedBase generator3 = increment -> new byte[] {126, 9, 35, 76, 2, 1, 2};
+ private RequestUidGeneratorBase uidGeneratorBase = mock(RequestUidGeneratorBase.class);
+ private IllegalStateException expected = new IllegalStateException("Unexpected third call to createNew");
+
+ @Test
+ public void generate_renews_inner_UuidGenerator_instance_every_number_of_calls_to_generate_specified_in_RequestIdConfiguration_supports_2() {
+ when(uidGeneratorBase.createNew())
+ .thenReturn(generator1)
+ .thenReturn(generator2)
+ .thenReturn(generator3)
+ .thenThrow(expected);
+
+ RequestUidGeneratorImpl underTest = new RequestUidGeneratorImpl(uidGeneratorBase, new RequestIdConfiguration(2));
+
+ assertThat(underTest.generate()).isEqualTo("fBZCYDdYAgk="); // using generator1
+ assertThat(underTest.generate()).isEqualTo("fBZCYDdYAgk="); // still using generator1
+ assertThat(underTest.generate()).isEqualTo("AAVYUQgGLBM="); // renewing generator and using generator2
+ assertThat(underTest.generate()).isEqualTo("AAVYUQgGLBM="); // still using generator2
+ assertThat(underTest.generate()).isEqualTo("fgkjTAIBAg=="); // renewing generator and using generator3
+ assertThat(underTest.generate()).isEqualTo("fgkjTAIBAg=="); // using generator3
+
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage(expected.getMessage());
+
+ underTest.generate(); // renewing generator and failing
+ }
+
@Test
- public void generate_returns_value_if_RequestUidGeneratorBase_generate_encoded_in_base64() {
- RequestUidGeneratorImpl underTest = new RequestUidGeneratorImpl(increment -> new byte[] {124, 22, 66, 96, 55, 88, 2, 9});
+ public void generate_renews_inner_UuidGenerator_instance_every_number_of_calls_to_generate_specified_in_RequestIdConfiguration_supports_3() {
+ when(uidGeneratorBase.createNew())
+ .thenReturn(generator1)
+ .thenReturn(generator2)
+ .thenReturn(generator3)
+ .thenThrow(expected);
+
+ RequestUidGeneratorImpl underTest = new RequestUidGeneratorImpl(uidGeneratorBase, new RequestIdConfiguration(3));
+
+ assertThat(underTest.generate()).isEqualTo("fBZCYDdYAgk="); // using generator1
+ assertThat(underTest.generate()).isEqualTo("fBZCYDdYAgk="); // still using generator1
+ assertThat(underTest.generate()).isEqualTo("fBZCYDdYAgk="); // still using generator1
+ assertThat(underTest.generate()).isEqualTo("AAVYUQgGLBM="); // renewing generator and using it
+ assertThat(underTest.generate()).isEqualTo("AAVYUQgGLBM="); // still using generator2
+ assertThat(underTest.generate()).isEqualTo("AAVYUQgGLBM="); // still using generator2
+ assertThat(underTest.generate()).isEqualTo("fgkjTAIBAg=="); // renewing generator and using it
+ assertThat(underTest.generate()).isEqualTo("fgkjTAIBAg=="); // using generator3
+ assertThat(underTest.generate()).isEqualTo("fgkjTAIBAg=="); // using generator3
+
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage(expected.getMessage());
- assertThat(underTest.generate()).isEqualTo("fBZCYDdYAgk=");
+ underTest.generate(); // renewing generator and failing
}
}
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
package org.sonar.server.platform.web.requestid;
import org.apache.log4j.MDC;