Make SingleSelectCapability thread-safe

Closes gh-1105
This commit is contained in:
Andy Wilkinson 2020-06-18 16:52:06 +01:00
parent 9f19c6fa50
commit 42b701ab1a
4 changed files with 66 additions and 23 deletions

View File

@ -126,7 +126,7 @@ public class InitializrMetadataTestBuilder {
packaging.setId(id); packaging.setId(id);
packaging.setName(id); packaging.setName(id);
packaging.setDefault(defaultValue); packaging.setDefault(defaultValue);
it.getPackagings().getContent().add(packaging); it.getPackagings().addContent(packaging);
}); });
return this; return this;
} }
@ -141,7 +141,7 @@ public class InitializrMetadataTestBuilder {
element.setId(version); element.setId(version);
element.setName(version); element.setName(version);
element.setDefault(defaultValue); element.setDefault(defaultValue);
it.getJavaVersions().getContent().add(element); it.getJavaVersions().addContent(element);
}); });
return this; return this;
} }
@ -156,7 +156,7 @@ public class InitializrMetadataTestBuilder {
element.setId(id); element.setId(id);
element.setName(id); element.setName(id);
element.setDefault(defaultValue); element.setDefault(defaultValue);
it.getLanguages().getContent().add(element); it.getLanguages().addContent(element);
}); });
return this; return this;
} }
@ -172,7 +172,7 @@ public class InitializrMetadataTestBuilder {
element.setId(id); element.setId(id);
element.setName(id); element.setName(id);
element.setDefault(defaultValue); element.setDefault(defaultValue);
it.getBootVersions().getContent().add(element); it.getBootVersions().addContent(element);
}); });
return this; return this;
} }

View File

@ -200,8 +200,7 @@ public class InitializrMetadata {
* @param versionsMetadata the Spring Boot boot versions metadata to use * @param versionsMetadata the Spring Boot boot versions metadata to use
*/ */
public void updateSpringBootVersions(List<DefaultMetadataElement> versionsMetadata) { public void updateSpringBootVersions(List<DefaultMetadataElement> versionsMetadata) {
this.bootVersions.getContent().clear(); this.bootVersions.setContent(versionsMetadata);
this.bootVersions.getContent().addAll(versionsMetadata);
List<Version> bootVersions = this.bootVersions.getContent().stream().map((it) -> Version.parse(it.getId())) List<Version> bootVersions = this.bootVersions.getContent().stream().map((it) -> Version.parse(it.getId()))
.collect(Collectors.toList()); .collect(Collectors.toList());
VersionParser parser = new VersionParser(bootVersions); VersionParser parser = new VersionParser(bootVersions);

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2019 the original author or authors. * Copyright 2012-2020 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,8 +16,13 @@
package io.spring.initializr.metadata; package io.spring.initializr.metadata;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Consumer;
import java.util.function.Function;
import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
@ -30,7 +35,9 @@ import com.fasterxml.jackson.annotation.JsonProperty;
public class SingleSelectCapability extends ServiceCapability<List<DefaultMetadataElement>> public class SingleSelectCapability extends ServiceCapability<List<DefaultMetadataElement>>
implements Defaultable<DefaultMetadataElement> { implements Defaultable<DefaultMetadataElement> {
private final List<DefaultMetadataElement> content = new CopyOnWriteArrayList<>(); private final List<DefaultMetadataElement> content = new ArrayList<>();
private final ReadWriteLock contentLock = new ReentrantReadWriteLock();
@JsonCreator @JsonCreator
SingleSelectCapability(@JsonProperty("id") String id) { SingleSelectCapability(@JsonProperty("id") String id) {
@ -43,7 +50,18 @@ public class SingleSelectCapability extends ServiceCapability<List<DefaultMetada
@Override @Override
public List<DefaultMetadataElement> getContent() { public List<DefaultMetadataElement> getContent() {
return this.content; return Collections.unmodifiableList(withReadableContent(ArrayList::new));
}
public void addContent(DefaultMetadataElement element) {
withWritableContent((content) -> content.add(element));
}
public void setContent(List<DefaultMetadataElement> newContent) {
withWritableContent((content) -> {
content.clear();
content.addAll(newContent);
});
} }
/** /**
@ -51,7 +69,8 @@ public class SingleSelectCapability extends ServiceCapability<List<DefaultMetada
*/ */
@Override @Override
public DefaultMetadataElement getDefault() { public DefaultMetadataElement getDefault() {
return this.content.stream().filter(DefaultMetadataElement::isDefault).findFirst().orElse(null); return withReadableContent(
(content) -> content.stream().filter(DefaultMetadataElement::isDefault).findFirst().orElse(null));
} }
/** /**
@ -60,16 +79,39 @@ public class SingleSelectCapability extends ServiceCapability<List<DefaultMetada
* @return the element or {@code null} * @return the element or {@code null}
*/ */
public DefaultMetadataElement get(String id) { public DefaultMetadataElement get(String id) {
return this.content.stream().filter((it) -> id.equals(it.getId())).findFirst().orElse(null); return withReadableContent(
(content) -> content.stream().filter((it) -> id.equals(it.getId())).findFirst().orElse(null));
} }
@Override @Override
public void merge(List<DefaultMetadataElement> otherContent) { public void merge(List<DefaultMetadataElement> otherContent) {
withWritableContent((content) -> {
otherContent.forEach((it) -> { otherContent.forEach((it) -> {
if (get(it.getId()) == null) { if (get(it.getId()) == null) {
this.content.add(it); this.content.add(it);
} }
}); });
});
}
private <T> T withReadableContent(Function<List<DefaultMetadataElement>, T> consumer) {
this.contentLock.readLock().lock();
try {
return consumer.apply(this.content);
}
finally {
this.contentLock.readLock().unlock();
}
}
private void withWritableContent(Consumer<List<DefaultMetadataElement>> consumer) {
this.contentLock.writeLock().lock();
try {
consumer.accept(this.content);
}
finally {
this.contentLock.writeLock().unlock();
}
} }
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2019 the original author or authors. * Copyright 2012-2020 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,6 +16,8 @@
package io.spring.initializr.metadata; package io.spring.initializr.metadata;
import java.util.Arrays;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -34,17 +36,17 @@ class SingleSelectCapabilityTests {
@Test @Test
void defaultNoDefault() { void defaultNoDefault() {
SingleSelectCapability capability = new SingleSelectCapability("test"); SingleSelectCapability capability = new SingleSelectCapability("test");
capability.getContent().add(DefaultMetadataElement.create("foo", false)); capability.setContent(Arrays.asList(DefaultMetadataElement.create("foo", false),
capability.getContent().add(DefaultMetadataElement.create("bar", false)); DefaultMetadataElement.create("bar", false)));
assertThat(capability.getDefault()).isNull(); assertThat(capability.getDefault()).isNull();
} }
@Test @Test
void defaultType() { void defaultType() {
SingleSelectCapability capability = new SingleSelectCapability("test"); SingleSelectCapability capability = new SingleSelectCapability("test");
capability.getContent().add(DefaultMetadataElement.create("foo", false)); DefaultMetadataElement first = DefaultMetadataElement.create("foo", false);
DefaultMetadataElement second = DefaultMetadataElement.create("bar", true); DefaultMetadataElement second = DefaultMetadataElement.create("bar", true);
capability.getContent().add(second); capability.setContent(Arrays.asList(first, second));
assertThat(capability.getDefault()).isEqualTo(second); assertThat(capability.getDefault()).isEqualTo(second);
} }
@ -52,11 +54,11 @@ class SingleSelectCapabilityTests {
void mergeAddEntry() { void mergeAddEntry() {
SingleSelectCapability capability = new SingleSelectCapability("test"); SingleSelectCapability capability = new SingleSelectCapability("test");
DefaultMetadataElement foo = DefaultMetadataElement.create("foo", false); DefaultMetadataElement foo = DefaultMetadataElement.create("foo", false);
capability.getContent().add(foo); capability.setContent(Arrays.asList(foo));
SingleSelectCapability anotherCapability = new SingleSelectCapability("test"); SingleSelectCapability anotherCapability = new SingleSelectCapability("test");
DefaultMetadataElement bar = DefaultMetadataElement.create("bar", false); DefaultMetadataElement bar = DefaultMetadataElement.create("bar", false);
anotherCapability.getContent().add(bar); anotherCapability.setContent(Arrays.asList(bar));
capability.merge(anotherCapability); capability.merge(anotherCapability);
assertThat(capability.getContent()).hasSize(2); assertThat(capability.getContent()).hasSize(2);