diff --git a/pom.xml b/pom.xml index 3fad1eb..0b8b50d 100644 --- a/pom.xml +++ b/pom.xml @@ -121,6 +121,10 @@ org.springframework spring-context + + org.springframework + spring-tx + diff --git a/src/main/java/com/avanza/gs/test/MirrorPu.java b/src/main/java/com/avanza/gs/test/MirrorPu.java index aa8f7b6..9c22115 100644 --- a/src/main/java/com/avanza/gs/test/MirrorPu.java +++ b/src/main/java/com/avanza/gs/test/MirrorPu.java @@ -20,6 +20,7 @@ import org.openspaces.pu.container.integrated.IntegratedProcessingUnitContainer; import org.openspaces.pu.container.integrated.IntegratedProcessingUnitContainerProvider; import org.springframework.context.ApplicationContext; +import org.springframework.core.io.Resource; import java.io.IOException; import java.util.Properties; @@ -29,6 +30,7 @@ public class MirrorPu implements PuRunner { private IntegratedProcessingUnitContainer container; private final String gigaSpaceBeanName = "gigaSpace"; private final String puXmlPath; + private final Resource puConfigResource; private Properties contextProperties = new Properties(); private final String lookupGroupName; private final boolean autostart; @@ -36,6 +38,7 @@ public class MirrorPu implements PuRunner { public MirrorPu(MirrorPuConfigurer config) { this.puXmlPath = config.puXmlPath; + this.puConfigResource = config.puConfigResource; this.contextProperties = config.properties; this.lookupGroupName = config.lookupGroupName; this.autostart = true; @@ -56,7 +59,12 @@ public void run() throws IOException { private void startContainers() throws IOException { IntegratedProcessingUnitContainerProvider provider = new IntegratedProcessingUnitContainerProvider(); provider.setBeanLevelProperties(createBeanLevelProperties()); - provider.addConfigLocation(puXmlPath); + if (puXmlPath != null) { + provider.addConfigLocation(puXmlPath); + } + if (puConfigResource != null) { + provider.addConfigLocation(puConfigResource); + } if (parentContext != null) { provider.setParentContext(parentContext); } diff --git a/src/main/java/com/avanza/gs/test/MirrorPuConfigurer.java b/src/main/java/com/avanza/gs/test/MirrorPuConfigurer.java index 577ce43..d8a433a 100644 --- a/src/main/java/com/avanza/gs/test/MirrorPuConfigurer.java +++ b/src/main/java/com/avanza/gs/test/MirrorPuConfigurer.java @@ -18,16 +18,24 @@ import java.util.Properties; import org.springframework.context.ApplicationContext; +import org.springframework.core.io.Resource; public class MirrorPuConfigurer { final String puXmlPath; + final Resource puConfigResource; Properties properties = new Properties(); ApplicationContext parentContext; String lookupGroupName = JVMGlobalLus.getLookupGroupName(); public MirrorPuConfigurer(String puXmlPath) { this.puXmlPath = puXmlPath; + this.puConfigResource = null; + } + + public MirrorPuConfigurer(Resource puConfigResource) { + this.puXmlPath = null; + this.puConfigResource = puConfigResource; } /** diff --git a/src/main/java/com/avanza/gs/test/PartitionedPu.java b/src/main/java/com/avanza/gs/test/PartitionedPu.java index 20d0b86..7d068ed 100644 --- a/src/main/java/com/avanza/gs/test/PartitionedPu.java +++ b/src/main/java/com/avanza/gs/test/PartitionedPu.java @@ -27,6 +27,8 @@ import org.openspaces.pu.container.integrated.IntegratedProcessingUnitContainerProvider; import org.openspaces.pu.container.support.CompoundProcessingUnitContainer; import org.springframework.context.ApplicationContext; +import org.springframework.core.io.Resource; + import com.gigaspaces.security.directory.DefaultCredentialsProvider; /** @@ -39,6 +41,7 @@ public final class PartitionedPu implements PuRunner { private CompoundProcessingUnitContainer container; private final String gigaSpaceBeanName = "gigaSpace"; private final String puXmlPath; + private final Resource puConfigResource; private final Integer numberOfPrimaries; private final Integer numberOfBackups; private final Properties contextProperties = new Properties(); @@ -50,6 +53,7 @@ public final class PartitionedPu implements PuRunner { public PartitionedPu(PartitionedPuConfigurer configurer) { this.puXmlPath = configurer.puXmlPath; + this.puConfigResource = configurer.puConfigResource; this.numberOfBackups = configurer.numberOfBackups; this.numberOfPrimaries = configurer.numberOfPrimaries; this.contextProperties.putAll(configurer.contextProperties); @@ -76,7 +80,12 @@ private void startContainers() throws IOException { IntegratedProcessingUnitContainerProvider provider = new IntegratedProcessingUnitContainerProvider(); provider.setBeanLevelProperties(createBeanLevelProperties()); provider.setClusterInfo(createClusterInfo()); - provider.addConfigLocation(puXmlPath); + if (puXmlPath != null) { + provider.addConfigLocation(puXmlPath); + } + if (puConfigResource != null) { + provider.addConfigLocation(puConfigResource); + } if (parentContext != null) { provider.setParentContext(parentContext); } diff --git a/src/main/java/com/avanza/gs/test/PartitionedPuConfigurer.java b/src/main/java/com/avanza/gs/test/PartitionedPuConfigurer.java index 5e455b7..9ca7a82 100644 --- a/src/main/java/com/avanza/gs/test/PartitionedPuConfigurer.java +++ b/src/main/java/com/avanza/gs/test/PartitionedPuConfigurer.java @@ -20,10 +20,12 @@ import java.util.Properties; import org.springframework.context.ApplicationContext; +import org.springframework.core.io.Resource; public final class PartitionedPuConfigurer { - String puXmlPath; + final String puXmlPath; + final Resource puConfigResource; int numberOfPrimaries = 1; int numberOfBackups = 0; boolean startAsync = false; @@ -37,6 +39,12 @@ public final class PartitionedPuConfigurer { public PartitionedPuConfigurer(String puXmlPath) { this.puXmlPath = puXmlPath; + this.puConfigResource = null; + } + + public PartitionedPuConfigurer(Resource puConfigResource) { + this.puXmlPath = null; + this.puConfigResource = puConfigResource; } public PartitionedPuConfigurer parentContext(ApplicationContext parentContext) { diff --git a/src/main/java/com/avanza/gs/test/PartitionedSpace.java b/src/main/java/com/avanza/gs/test/PartitionedSpace.java new file mode 100644 index 0000000..7b4e987 --- /dev/null +++ b/src/main/java/com/avanza/gs/test/PartitionedSpace.java @@ -0,0 +1,215 @@ +/* + * Copyright 2017 Avanza Bank AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.avanza.gs.test; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; +import org.openspaces.core.GigaSpace; +import org.openspaces.core.space.UrlSpaceConfigurer; +import org.springframework.core.io.FileSystemResource; +import org.springframework.util.StreamUtils; + +/** + * Utility class to create a partitioned space without backups. + *

+ * Individual primary instances can be retrieved by using the {@code partition(ID)} method. An url to the + * cluster can be retrieved through the {@code getUrl()} method. + * + * @deprecated This class is provided for backwards compatibility. + * For a simple test with no need for partitions, {@link EmbeddedSpace} can be used instead. + * For more complex tests, use {@link PuConfigurers} to set up a {@code partitionedPu}. + */ +@Deprecated +public class PartitionedSpace implements TestRule { + + private final GigaSpace[] instances; + private final RunningPu partitionedPu; + private final Path puXml; + private final String spaceName; + private final String groupName; + private final int numberOfPartitions; + + private boolean started = false; + + /** + * Creates a partitioned space with a given number of partitions. + * + * @param numberOfPartitions (must be at least 2). + * @throws IllegalArgumentException if numberOfPartitions is less than 2. + */ + public PartitionedSpace(int numberOfPartitions) { + this(numberOfPartitions, "testSpace"); + } + + /** + * Creates a partitioned space with a given number of partitions. + * + * @param numberOfPartitions (must be at least 2). + * @param spaceName name of the space to create. + * + * @throws IllegalArgumentException if numberOfPartitions is less than 2. + * @throws IllegalArgumentException if spaceName is empty string. + * @throws NullPointerException if spaceName is null. + */ + public PartitionedSpace(int numberOfPartitions, String spaceName) { + this(numberOfPartitions, spaceName, JVMGlobalLus.getLookupGroupName()); + } + + public PartitionedSpace(int numberOfPartitions, String spaceName, String lookupGroupName) { + if (numberOfPartitions < 2) { + throw new IllegalArgumentException("Number of partitions must be at least 2, was " + numberOfPartitions); + } + if (spaceName.isEmpty()) { + throw new IllegalArgumentException("Space name must not be empty"); + } + this.numberOfPartitions = numberOfPartitions; + this.groupName = lookupGroupName; + this.spaceName = spaceName; + this.instances = new GigaSpace[numberOfPartitions]; + this.puXml = writePuXml(spaceName); + this.partitionedPu = PuConfigurers.partitionedPu(new FileSystemResource(puXml.toFile())) + .lookupGroup(lookupGroupName) + .spaceName(spaceName) + .numberOfPrimaries(numberOfPartitions) + .numberOfBackups(0) + .configure(); + + start(); + } + + private static Path writePuXml(String spaceName) { + try (InputStream in = PartitionedSpace.class.getResourceAsStream("/partitioned_space/simple-pu.xml.template")) { + Path tempFile = Files.createTempFile("partitioned-space-", ".xml"); + String content = StreamUtils.copyToString(in, UTF_8) + .replace("##SPACE_NAME##", spaceName); + return Files.write(tempFile, content.getBytes(UTF_8)); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void start() { + if (started) { + return; + } + try { + partitionedPu.start(); + } catch (Exception e) { + throw new RuntimeException("Could not start partitionedPu", e); + } + for (int instanceId = 1; instanceId <= numberOfPartitions; instanceId ++) { + instances[instanceId - 1] = partitionedPu.getPrimaryInstanceApplicationContext(instanceId - 1) + .getBean(GigaSpace.class); + } + started = true; + } + + @Deprecated + protected void configure(UrlSpaceConfigurer spaceConfigurer) { + throw new UnsupportedOperationException("This method is not supported"); + } + + /** + * Returns the primary with the given instanceId. + * + * NOTE: Primaries are numbered from 1! + */ + public GigaSpace primary(int instanceId) { + if (instanceId < 1) { + throw new IllegalArgumentException("Primaries are numbered from 1! Requested instance was " + instanceId); + } + if (instanceId > instances.length) { + throw new IllegalArgumentException("No such primary: " + instanceId + ". Space contains " + this.instances.length + " partitions."); + } + return instances[instanceId - 1]; + } + + private static final Object ALL_OBJECTS = null; /* Intentional NULL */ + + /** + * Cleans all space's in this cluster. + */ + public void clean() { + for (GigaSpace instance : instances) { + instance.clear(ALL_OBJECTS); + } + } + + /** + * Returns an url to this space. + */ + public String getUrl() { + return "jini://*/*/" + this.spaceName + "?groups=" + this.groupName; + } + + /** + * Creates a clustered proxy against this space. + */ + public GigaSpace createClusteredProxy() { + return partitionedPu.getClusteredGigaSpace(); + } + + /** + * Destroys this space. + */ + public void destroy() { + try { + partitionedPu.stop(); + Files.deleteIfExists(puXml); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Returns the name of this space. + */ + public String getName() { + return this.spaceName; + } + + /** + * Returns the lookup group name + */ + public String getLookupGroupName() { + return this.groupName; + } + + @Override + public Statement apply(final Statement base, Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + try { + start(); + base.evaluate(); + } finally { + destroy(); + } + } + }; + } + +} diff --git a/src/main/java/com/avanza/gs/test/PuConfigurers.java b/src/main/java/com/avanza/gs/test/PuConfigurers.java index 500bdad..dc9d4b6 100644 --- a/src/main/java/com/avanza/gs/test/PuConfigurers.java +++ b/src/main/java/com/avanza/gs/test/PuConfigurers.java @@ -15,14 +15,24 @@ */ package com.avanza.gs.test; +import org.springframework.core.io.Resource; + public class PuConfigurers { public static PartitionedPuConfigurer partitionedPu(String puXmlPath) { return new PartitionedPuConfigurer(puXmlPath); } + public static PartitionedPuConfigurer partitionedPu(Resource puConfigResource) { + return new PartitionedPuConfigurer(puConfigResource); + } + public static MirrorPuConfigurer mirrorPu(String puXmlPath) { return new MirrorPuConfigurer(puXmlPath); } + public static MirrorPuConfigurer mirrorPu(Resource puConfigResource) { + return new MirrorPuConfigurer(puConfigResource); + } + } diff --git a/src/main/resources/partitioned_space/simple-pu.xml.template b/src/main/resources/partitioned_space/simple-pu.xml.template new file mode 100644 index 0000000..a5190c3 --- /dev/null +++ b/src/main/resources/partitioned_space/simple-pu.xml.template @@ -0,0 +1,21 @@ + + + + + + + + 1 + read-only + true + true + + + + + diff --git a/src/test/java/com/avanza/gs/test/PartitionedSpaceTest.java b/src/test/java/com/avanza/gs/test/PartitionedSpaceTest.java new file mode 100644 index 0000000..e71cb27 --- /dev/null +++ b/src/test/java/com/avanza/gs/test/PartitionedSpaceTest.java @@ -0,0 +1,175 @@ +/* + * Copyright 2017 Avanza Bank AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.avanza.gs.test; + +import static org.hamcrest.CoreMatchers.startsWith; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Ignore; +import org.junit.Test; +import org.openspaces.core.GigaSpace; +import org.openspaces.core.space.UrlSpaceFactoryBean; + +import com.gigaspaces.annotation.pojo.SpaceId; +import com.gigaspaces.annotation.pojo.SpaceRouting; +import com.j_spaces.core.IJSpace; + +@Ignore("requires a valid gs license to be set with -Dcom.gs.licensekey to run") +public class PartitionedSpaceTest { + + private static final String SPACE_NAME = "the_space_name"; + + @ClassRule + public static final PartitionedSpace space = new PartitionedSpace(2, SPACE_NAME); + + private GigaSpace clustered; + private GigaSpace partition1; + private GigaSpace partition2; + + @Before + public void setup() { + clustered = space.createClusteredProxy(); + partition1 = space.primary(1); + partition2 = space.primary(2); + } + + @After + public void cleanSpace() { + space.clean(); + } + + @AfterClass + public static void destroySpace() { + space.destroy(); + } + + @Test + public void writeToPartition1() { + assertEquals("expected space to be empty before insert", 0, clustered.count(null)); + + clustered.write(createMessage("foo", 0)); + assertEquals(1, partition1.count(null)); + assertEquals(0, partition2.count(null)); + assertEquals(1, clustered.count(null)); + } + + @Test + public void writeToPartition2() { + assertEquals("expected space to be empty before insert", 0, clustered.count(null)); + + clustered.write(createMessage("foo", 1)); + assertEquals(0, partition1.count(null)); + assertEquals(1, partition2.count(null)); + assertEquals(1, clustered.count(null)); + } + + @Test(expected = IllegalArgumentException.class) + public void throwsIllegalArgumentExceptionWhenAskingForNonexistingPrimary1() { + space.primary(0); + } + + @Test(expected = IllegalArgumentException.class) + public void throwsIllegalArgumentExceptionWhenAskingForNonexistingPrimary2() { + space.primary(3); + } + + @Test(expected = IllegalArgumentException.class) + public void throwsIllegalArgumentExceptionIfNumberOfPartitionsIsLessThan2() { + new PartitionedSpace(1); + } + + @Test(expected = IllegalArgumentException.class) + public void throwsIllegalArgumentExceptionIfSpaceNameIsEmpty() { + new PartitionedSpace(1, ""); + } + + @Test + public void cleansSpace() { + clustered.write(createMessage("foo", 0)); + clustered.write(createMessage("foo", 1)); + assertEquals(2, clustered.count(null)); + + space.clean(); + assertEquals(0, clustered.count(null)); + } + + @Test + public void spaceName() { + assertEquals("gigaSpace", partition1.getName()); + assertEquals(SPACE_NAME, space.getName()); + } + + @Test + public void lookupGroupname() { + assertNotNull(space.getLookupGroupName()); + } + + @Test + public void connectToSpaceWithUrl() { + UrlSpaceFactoryBean urlSpaceFactoryBean = new UrlSpaceFactoryBean(space.getUrl()); + urlSpaceFactoryBean.afterPropertiesSet(); + IJSpace obj = (IJSpace) urlSpaceFactoryBean.getObject(); + assertThat(obj.getContainerName(), startsWith(SPACE_NAME)); + } + + private Message createMessage(String message, int routingId) { + Message m = new Message(); + m.setMessage(message); + m.setRoutingId(routingId); + return m; + } + + public static class Message { + + private String message; + private int routingId; + private String id; + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + @SpaceRouting + public int getRoutingId() { + return routingId; + } + + public void setRoutingId(int routingId) { + this.routingId = routingId; + } + + public void setId(String id) { + this.id = id; + } + + @SpaceId(autoGenerate = true) + public String getId() { + return id; + } + + } + +}