diff --git a/pom.xml b/pom.xml index f36edda3f..649bfcd78 100644 --- a/pom.xml +++ b/pom.xml @@ -32,6 +32,7 @@ 1.7.0.BUILD-SNAPSHOT 3.3.0.BUILD-SNAPSHOT 1.6.0.BUILD-SNAPSHOT + 1.4.0.BUILD-SNAPSHOT 4.3.5.Final @@ -109,6 +110,13 @@ test + + org.springframework.data + spring-data-solr + ${springdata.solr} + test + + org.hsqldb hsqldb diff --git a/spring-data-rest-webmvc/pom.xml b/spring-data-rest-webmvc/pom.xml index 26fd1ced8..f833f12e9 100644 --- a/spring-data-rest-webmvc/pom.xml +++ b/spring-data-rest-webmvc/pom.xml @@ -110,6 +110,23 @@ test + + org.apache.solr + solr-core + 4.7.2 + test + + + org.slf4j + slf4j-api + + + jdk.tools + jdk.tools + + + + diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/AbstractWebIntegrationTests.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/AbstractWebIntegrationTests.java index 9b276b00b..dd216274f 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/AbstractWebIntegrationTests.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/AbstractWebIntegrationTests.java @@ -57,6 +57,7 @@ import com.jayway.jsonpath.JsonPath; * * @author Oliver Gierke * @author Greg Turnquist + * @author Christoph Strobl */ @RunWith(SpringJUnit4ClassRunner.class) @WebAppConfiguration @@ -205,10 +206,18 @@ public abstract class AbstractWebIntegrationTests { protected String assertJsonPathEquals(String path, String expected, MockHttpServletResponse response) throws Exception { - String jsonQueryResults = assertHasJsonPathValue(path, response); - assertThat(jsonQueryResults, is(expected)); + Object jsonQueryResults = assertHasJsonPathValue(path, response); - return jsonQueryResults; + String jsonString = ""; + + if (jsonQueryResults instanceof JSONArray) { + jsonString = ((JSONArray) jsonQueryResults).toJSONString(); + } else { + jsonString = jsonQueryResults != null ? jsonQueryResults.toString() : null; + } + + assertThat(jsonString, is(expected)); + return jsonString; } protected ResultMatcher doesNotHaveLinkWithRel(final String rel) { diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/solr/Product.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/solr/Product.java new file mode 100644 index 000000000..a955682b1 --- /dev/null +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/solr/Product.java @@ -0,0 +1,69 @@ +/* + * Copyright 2014 the original author or authors. + * + * 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 org.springframework.data.rest.webmvc.solr; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.springframework.data.annotation.Id; +import org.springframework.data.solr.core.mapping.Indexed; +import org.springframework.data.solr.core.mapping.SolrDocument; +import org.springframework.util.ObjectUtils; + +/** + * @author Christoph Strobl + */ +@SolrDocument(solrCoreName = "collection1") +public class Product { + + private @Id String id; + private @Indexed String name; + private @Indexed(name = "cat") List categories; + + public Product() {} + + public Product(String id, String name, String... categories) { + + this.id = id; + this.name = name; + this.categories = ObjectUtils.isEmpty(categories) ? Collections. emptyList() : Arrays.asList(categories); + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getCategories() { + return categories; + } + + public void setCategories(List categories) { + this.categories = categories; + } +} diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/solr/ProductRepository.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/solr/ProductRepository.java new file mode 100644 index 000000000..097d8c62c --- /dev/null +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/solr/ProductRepository.java @@ -0,0 +1,23 @@ +/* + * Copyright 2014 the original author or authors. + * + * 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 org.springframework.data.rest.webmvc.solr; + +import org.springframework.data.repository.PagingAndSortingRepository; + +/** + * @author Christoph Strobl + */ +public interface ProductRepository extends PagingAndSortingRepository {} diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/solr/SolrInfrastructureConfig.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/solr/SolrInfrastructureConfig.java new file mode 100644 index 000000000..3ba66d249 --- /dev/null +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/solr/SolrInfrastructureConfig.java @@ -0,0 +1,87 @@ +/* + * Copyright 2014 the original author or authors. + * + * 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 org.springframework.data.rest.webmvc.solr; + +import static org.springframework.data.rest.webmvc.util.TestUtils.*; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import javax.xml.parsers.ParserConfigurationException; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.data.solr.core.SolrTemplate; +import org.springframework.data.solr.server.SolrServerFactory; +import org.springframework.data.solr.server.support.EmbeddedSolrServerFactory; +import org.springframework.util.FileCopyUtils; +import org.xml.sax.SAXException; + +/** + * @author Christoph Strobl + */ +@Configuration +public class SolrInfrastructureConfig { + + private static final String CORE_PROPERTIES = "name=collection1"; + private static final Resource SOLR_CONFIG = new ClassPathResource("solrconfig.xml", SolrInfrastructureConfig.class); + private static final Resource SOLR_SCHEMA = new ClassPathResource("schema.xml", SolrInfrastructureConfig.class); + + @Bean + public SolrServerFactory solrServerFactory(final String solrHomeDir) throws ParserConfigurationException, + IOException, SAXException { + + prepareConfiguration(solrHomeDir); + return new EmbeddedSolrServerFactory(solrHomeDir); + } + + @Bean + public SolrTemplate solrTemplate(SolrServerFactory factory) { + return new SolrTemplate(factory); + } + + private static void prepareConfiguration(final String solrHomeDir) throws IOException { + + Map configParams = new HashMap(); + configParams.put("${data.dir}", solrHomeDir); + configParams.put("${lucene.version}", "4.7"); + + Resource solrConfig = filterResource(SOLR_CONFIG, configParams); + Resource solrSchema = SOLR_SCHEMA; + + File confDir = new File(new File(solrHomeDir, "collection1"), "conf"); + confDir.mkdirs(); + + FileCopyUtils.copy(solrSchema.getInputStream(), new FileOutputStream(createFile(confDir, "schema.xml"))); + FileCopyUtils.copy(solrConfig.getInputStream(), new FileOutputStream(createFile(confDir, "solrconfig.xml"))); + FileCopyUtils.copy(CORE_PROPERTIES.getBytes(), + new FileOutputStream(createFile(new File(solrHomeDir), "config.properties"))); + } + + private static File createFile(File parent, String child) throws IOException { + + File file = new File(parent, child); + if (!file.exists()) { + file.createNewFile(); + } + return file; + } +} diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/solr/SolrTestBase.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/solr/SolrTestBase.java new file mode 100644 index 000000000..641432150 --- /dev/null +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/solr/SolrTestBase.java @@ -0,0 +1,44 @@ +/* + * Copyright 2014 the original author or authors. + * + * 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 org.springframework.data.rest.webmvc.solr; + +import org.junit.ClassRule; +import org.junit.rules.TemporaryFolder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.solr.repository.config.EnableSolrRepositories; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Christoph Strobl + */ +@ContextConfiguration +public class SolrTestBase { + + public static @ClassRule TemporaryFolder TEMP_FOLDER = new TemporaryFolder(); + + @Configuration + @EnableSolrRepositories + @Import(SolrInfrastructureConfig.class) + static class MyConf { + + @Bean + String solrHomeDir() { + return TEMP_FOLDER.getRoot().getAbsolutePath(); + } + } +} diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/solr/SolrWebTests.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/solr/SolrWebTests.java new file mode 100644 index 000000000..ad5c6a154 --- /dev/null +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/solr/SolrWebTests.java @@ -0,0 +1,154 @@ +/* + * Copyright 2014 the original author or authors. + * + * 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 org.springframework.data.rest.webmvc.solr; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import java.util.Arrays; + +import org.junit.After; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.rest.webmvc.AbstractWebIntegrationTests; +import org.springframework.data.solr.repository.config.EnableSolrRepositories; +import org.springframework.hateoas.Link; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.context.ContextConfiguration; + +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * @author Christoph Strobl + */ +@ContextConfiguration(classes = { SolrWebTests.MyConf.class }) +public class SolrWebTests extends AbstractWebIntegrationTests { + + public static @ClassRule TemporaryFolder TEMP_FOLDER = new TemporaryFolder(); + + private static final Product PLAYSTATION = new Product("1", "playstation", "electronic", "game", "media"); + private static final Product GAMEBOY = new Product("2", "gameboy", "electronic"); + private static final Product AMIGA500 = new Product("3", "amiga500", "ancient"); + + private static final ObjectMapper MAPPER = new ObjectMapper(); + + @Configuration + @EnableSolrRepositories + @Import(value = { SolrInfrastructureConfig.class }) + static class MyConf { + + @Bean + String solrHomeDir() { + return TEMP_FOLDER.getRoot().getAbsolutePath(); + } + + } + + @Autowired ProductRepository repo; + + @Before + public void setUp() { + + super.setUp(); + repo.save(Arrays.asList(PLAYSTATION, GAMEBOY, AMIGA500)); + } + + @After + public void tearDown() { + repo.deleteAll(); + } + + /** + * @see DATAREST-387 + */ + @Test + public void allowsPaginationThroughData() throws Exception { + + MockHttpServletResponse response = client.request("/products?page=0&size=1"); + + Link nextLink = client.assertHasLinkWithRel(Link.REL_NEXT, response); + assertDoesNotHaveLinkWithRel(Link.REL_PREVIOUS, response); + + response = client.request(nextLink); + client.assertHasLinkWithRel(Link.REL_PREVIOUS, response); + nextLink = client.assertHasLinkWithRel(Link.REL_NEXT, response); + + response = client.request(nextLink); + client.assertHasLinkWithRel(Link.REL_PREVIOUS, response); + assertDoesNotHaveLinkWithRel(Link.REL_NEXT, response); + } + + /** + * @see DATAREST-387 + */ + @Test + public void allowsRetrievingDataById() throws Exception { + requestAndCompare(PLAYSTATION); + } + + /** + * @see DATAREST-387 + */ + @Test + public void createsEntitesCorrectly() throws Exception { + + Product product = new Product("4", "iWatch", "trends", "scary"); + + mvc.perform( + put("/products/{id}", 4).content(MAPPER.writeValueAsString(product)).contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()).andReturn().getResponse(); + + assertJsonDocumentMatches(product); + } + + /** + * @see DATAREST-387 + */ + @Test + public void deletesEntitiesCorrectly() throws Exception { + deleteAndVerify(new Link("/products/1")); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.rest.webmvc.AbstractWebIntegrationTests#expectedRootLinkRels() + */ + @Override + protected Iterable expectedRootLinkRels() { + return Arrays.asList("products"); + } + + private void assertJsonDocumentMatches(Product reference) throws Exception { + requestAndCompare(reference); + } + + private MockHttpServletResponse requestAndCompare(Product reference) throws Exception { + + MockHttpServletResponse response = client.request("/products/" + reference.getId()); + + assertJsonPathEquals("name", reference.getName(), response); + assertJsonPathEquals("categories", MAPPER.writeValueAsString(reference.getCategories()), response); + + return response; + } +} diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/util/TestUtils.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/util/TestUtils.java index 4e75d962c..e8c53a046 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/util/TestUtils.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/util/TestUtils.java @@ -16,27 +16,37 @@ package org.springframework.data.rest.webmvc.util; import java.io.ByteArrayInputStream; +import java.io.IOException; import java.io.InputStream; import java.nio.charset.Charset; +import java.util.Map; import java.util.Scanner; +import org.springframework.core.io.ByteArrayResource; import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; import org.springframework.data.rest.webmvc.jpa.JpaWebTests; import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StreamUtils; +import org.springframework.util.StringUtils; /** * Test helper methods. * * @author Oliver Gierke + * @author Christoph Strobl */ public class TestUtils { + private static final Charset UTF8 = Charset.forName("UTF-8"); + public static String readFileFromClasspath(String name) throws Exception { ClassPathResource file = new ClassPathResource(name, JpaWebTests.class); StringBuilder builder = new StringBuilder(); - Scanner scanner = new Scanner(file.getFile(), "UTF-8"); + Scanner scanner = new Scanner(file.getFile(), UTF8.name()); try { @@ -59,6 +69,31 @@ public class TestUtils { */ public static InputStream asStream(String source) { Assert.notNull(source, "Source string must not be null!"); - return new ByteArrayInputStream(source.getBytes(Charset.forName("UTF-8"))); + return new ByteArrayInputStream(source.getBytes(UTF8)); } + + /** + * Filters the given {@link Resource} by replacing values within. + * + * @param source must not be {@literal null}. + * @param replacements + * @return {@link Resource} with replaced values. + * @throws IOException + */ + public static Resource filterResource(Resource source, Map replacements) throws IOException { + + Assert.notNull(source, "Cannot filter 'null' resource"); + if (CollectionUtils.isEmpty(replacements)) { + return source; + } + + String temp = StreamUtils.copyToString(source.getInputStream(), UTF8); + + for (Map.Entry entry : replacements.entrySet()) { + temp = StringUtils.replace(temp, entry.getKey(), entry.getValue() != null ? entry.getValue().toString() : ""); + } + + return new ByteArrayResource(temp.getBytes(UTF8)); + } + } diff --git a/spring-data-rest-webmvc/src/test/resources/org/springframework/data/rest/webmvc/solr/schema.xml b/spring-data-rest-webmvc/src/test/resources/org/springframework/data/rest/webmvc/solr/schema.xml new file mode 100644 index 000000000..d4422dae5 --- /dev/null +++ b/spring-data-rest-webmvc/src/test/resources/org/springframework/data/rest/webmvc/solr/schema.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + id + diff --git a/spring-data-rest-webmvc/src/test/resources/org/springframework/data/rest/webmvc/solr/solrconfig.xml b/spring-data-rest-webmvc/src/test/resources/org/springframework/data/rest/webmvc/solr/solrconfig.xml new file mode 100644 index 000000000..bc38797bd --- /dev/null +++ b/spring-data-rest-webmvc/src/test/resources/org/springframework/data/rest/webmvc/solr/solrconfig.xml @@ -0,0 +1,22 @@ + + + ${data.dir} + + ${lucene.version} + + + ${solr.commitwithin.softcommit:true} + + + + + explicit + true + text + + + + +