diff --git a/spring-data-commons-core/pom.xml b/spring-data-commons-core/pom.xml index a2a1a2d18..cfc37809b 100644 --- a/spring-data-commons-core/pom.xml +++ b/spring-data-commons-core/pom.xml @@ -16,6 +16,7 @@ 2.5.0 1.0 1.1.3 + 1.9.7 @@ -32,6 +33,18 @@ ${org.springframework.version} true + + org.springframework + spring-oxm + ${org.springframework.version} + true + + + org.codehaus.jackson + jackson-mapper-asl + ${jackson.version} + true + org.springframework spring-web diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/RepositoryNameSpaceHandler.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/RepositoryNameSpaceHandler.java new file mode 100644 index 000000000..7c314088d --- /dev/null +++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/RepositoryNameSpaceHandler.java @@ -0,0 +1,40 @@ +/* + * Copyright 2012 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.repository.config; + +import org.springframework.beans.factory.xml.BeanDefinitionParser; +import org.springframework.beans.factory.xml.NamespaceHandler; +import org.springframework.beans.factory.xml.NamespaceHandlerSupport; + +/** + * {@link NamespaceHandler} to register {@link BeanDefinitionParser}s for repository initializers. + * + * @author Oliver Gierke + * @since 1.4 + */ +public class RepositoryNameSpaceHandler extends NamespaceHandlerSupport { + + private static final BeanDefinitionParser PARSER = new ResourceReaderRepositoryPopulatorBeanDefinitionParser(); + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.xml.NamespaceHandler#init() + */ + public void init() { + registerBeanDefinitionParser("unmarshaller-populator", PARSER); + registerBeanDefinitionParser("jackson-populator", PARSER); + } +} diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/ResourceReaderRepositoryPopulatorBeanDefinitionParser.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/ResourceReaderRepositoryPopulatorBeanDefinitionParser.java new file mode 100644 index 000000000..598790c1b --- /dev/null +++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/ResourceReaderRepositoryPopulatorBeanDefinitionParser.java @@ -0,0 +1,101 @@ +/* + * Copyright 2012 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.repository.config; + +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; +import org.springframework.beans.factory.xml.BeanDefinitionParser; +import org.springframework.data.repository.init.JacksonRepositoryPopulatorFactoryBean; +import org.springframework.data.repository.init.UnmarshallerRepositoryPopulatorFactoryBean; +import org.springframework.util.StringUtils; +import org.w3c.dom.Element; + +/** + * {@link BeanDefinitionParser} to parse repository initializers. + * + * @author Oliver Gierke + */ +public class ResourceReaderRepositoryPopulatorBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser#getBeanClassName(org.w3c.dom.Element) + */ + @Override + protected String getBeanClassName(Element element) { + + String name = element.getLocalName(); + return "unmarshaller-populator".equals(name) ? UnmarshallerRepositoryPopulatorFactoryBean.class.getName() + : JacksonRepositoryPopulatorFactoryBean.class.getName(); + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser#doParse(org.w3c.dom.Element, org.springframework.beans.factory.xml.ParserContext, org.springframework.beans.factory.support.BeanDefinitionBuilder) + */ + @Override + protected void doParse(Element element, BeanDefinitionBuilder builder) { + + String localName = element.getLocalName(); + + builder.addPropertyValue("resources", element.getAttribute("locations")); + + if ("unmarshaller-populator".equals(localName)) { + parseXmlPopulator(element, builder); + } else if ("jackson-populator".equals(localName)) { + parseJsonPopulator(element, builder); + } + } + + /** + * Populates the {@link BeanDefinitionBuilder} for a Jackson reader. + * + * @param element + * @param builder + */ + private void parseJsonPopulator(Element element, BeanDefinitionBuilder builder) { + + String objectMapperRef = element.getAttribute("object-mapper-ref"); + + if (StringUtils.hasText(objectMapperRef)) { + builder.addPropertyReference("mapper", objectMapperRef); + } + } + + /** + * Populate the {@link BeanDefinitionBuilder} for XML reader. + * + * @param element + * @param builder + */ + private void parseXmlPopulator(Element element, BeanDefinitionBuilder builder) { + + String unmarshallerRefName = element.getAttribute("unmarshaller-ref"); + + if (StringUtils.hasText(unmarshallerRefName)) { + builder.addPropertyReference("unmarshaller", unmarshallerRefName); + } + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.xml.AbstractBeanDefinitionParser#shouldGenerateIdAsFallback() + */ + @Override + protected boolean shouldGenerateIdAsFallback() { + return true; + } +} diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/init/AbstractRepositoryPopulatorFactoryBean.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/init/AbstractRepositoryPopulatorFactoryBean.java new file mode 100644 index 000000000..0276019cf --- /dev/null +++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/init/AbstractRepositoryPopulatorFactoryBean.java @@ -0,0 +1,82 @@ +/* + * Copyright 2012 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.repository.init; + +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.config.AbstractFactoryBean; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.core.io.Resource; +import org.springframework.data.repository.support.Repositories; + +/** + * {@link FactoryBean} to set up a {@link ResourceReaderRepositoryPopulator} with a {@link JacksonResourceReader}. + * + * @author Oliver Gierke + */ +public abstract class AbstractRepositoryPopulatorFactoryBean extends + AbstractFactoryBean implements ApplicationListener { + + private Resource[] resources; + private RepositoryPopulator populator; + + /** + * Configures the {@link Resource}s to be used to load objects from and initialize the repositories eventually. + * + * @param resources the resources to set + */ + public void setResources(Resource[] resources) { + this.resources = resources; + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.config.AbstractFactoryBean#getObjectType() + */ + @Override + public Class getObjectType() { + return ResourceReaderRepositoryPopulator.class; + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.config.AbstractFactoryBean#createInstance() + */ + @Override + protected ResourceReaderRepositoryPopulator createInstance() throws Exception { + + ResourceReaderRepositoryPopulator initializer = new ResourceReaderRepositoryPopulator(getResourceReader()); + initializer.setResources(resources); + + this.populator = initializer; + + return initializer; + } + + /* + * (non-Javadoc) + * @see org.springframework.context.ApplicationListener#onApplicationEvent(org.springframework.context.ApplicationEvent) + */ + public void onApplicationEvent(ContextRefreshedEvent event) { + + if (event.equals(getBeanFactory())) { + Repositories repositories = new Repositories(event.getApplicationContext()); + populator.populate(repositories); + } + } + + protected abstract ResourceReader getResourceReader(); +} diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/init/JacksonRepositoryPopulatorFactoryBean.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/init/JacksonRepositoryPopulatorFactoryBean.java new file mode 100644 index 000000000..bebd231f0 --- /dev/null +++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/init/JacksonRepositoryPopulatorFactoryBean.java @@ -0,0 +1,47 @@ +/* + * Copyright 2012 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.repository.init; + +import org.codehaus.jackson.map.ObjectMapper; +import org.springframework.beans.factory.FactoryBean; + +/** + * {@link FactoryBean} to set up a {@link ResourceReaderRepositoryPopulator} with a {@link JacksonResourceReader}. + * + * @author Oliver Gierke + */ +public class JacksonRepositoryPopulatorFactoryBean extends AbstractRepositoryPopulatorFactoryBean { + + private ObjectMapper mapper; + + /** + * Configures the {@link ObjectMapper} to be used. + * + * @param mapper + */ + public void setMapper(ObjectMapper mapper) { + this.mapper = mapper; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.init.AbstractRepositoryPopulatorFactoryBean#getResourceReader() + */ + @Override + protected ResourceReader getResourceReader() { + return new JacksonResourceReader(mapper); + } +} diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/init/JacksonResourceReader.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/init/JacksonResourceReader.java new file mode 100644 index 000000000..98adfdb3d --- /dev/null +++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/init/JacksonResourceReader.java @@ -0,0 +1,114 @@ +/* + * Copyright 2012 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.repository.init; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.codehaus.jackson.JsonNode; +import org.codehaus.jackson.map.DeserializationConfig.Feature; +import org.codehaus.jackson.map.ObjectMapper; +import org.springframework.core.io.Resource; +import org.springframework.util.ClassUtils; + +/** + * A {@link ResourceReader} using Jackson to read JSON into objects. + * + * @author Oliver Gierke + */ +public class JacksonResourceReader implements ResourceReader { + + private static final String DEFAULT_TYPE_KEY = "_class"; + private static final ObjectMapper DEFAULT_MAPPER = new ObjectMapper(); + + static { + DEFAULT_MAPPER.configure(Feature.FAIL_ON_UNKNOWN_PROPERTIES, false); + } + + private final ObjectMapper mapper; + private String typeKey = DEFAULT_TYPE_KEY; + + /** + * Creates a new {@link JacksonResourceReader}. + */ + public JacksonResourceReader() { + this(DEFAULT_MAPPER); + } + + /** + * Creates a new {@link JacksonResourceReader} using the given {@link ObjectMapper}. + * + * @param mapper + */ + public JacksonResourceReader(ObjectMapper mapper) { + this.mapper = mapper == null ? DEFAULT_MAPPER : mapper; + } + + /** + * Configures the JSON document's key to lookup the type to instantiate the object. Defaults to + * {@value JacksonResourceReader#DEFAULT_TYPE_KEY}. + * + * @param typeKey + */ + public void setTypeKey(String typeKey) { + this.typeKey = typeKey; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.init.ResourceReader#readFrom(org.springframework.core.io.Resource, java.lang.ClassLoader) + */ + public Object readFrom(Resource resource, ClassLoader classLoader) throws Exception { + + InputStream stream = resource.getInputStream(); + JsonNode node = mapper.reader(JsonNode.class).readTree(stream); + + if (node.isArray()) { + + Iterator elements = node.getElements(); + List result = new ArrayList(); + + while (elements.hasNext()) { + JsonNode element = elements.next(); + result.add(readSingle(element, classLoader)); + } + + return result; + } + + return readSingle(node, classLoader); + } + + /** + * Reads the given {@link JsonNode} into an instance of the type encoded in it using the configured type key. + * + * @param node must not be {@literal null}. + * @param classLoader + * @return + */ + private Object readSingle(JsonNode node, ClassLoader classLoader) throws IOException { + + JsonNode typeNode = node.findValue(typeKey); + String typeName = typeNode == null ? null : typeNode.asText(); + + Class type = ClassUtils.resolveClassName(typeName, classLoader); + + return mapper.reader(type).readValue(node); + } +} diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/init/RepositoryPopulator.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/init/RepositoryPopulator.java new file mode 100644 index 000000000..252e15534 --- /dev/null +++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/init/RepositoryPopulator.java @@ -0,0 +1,34 @@ +/* + * Copyright 2012 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.repository.init; + +import org.springframework.data.repository.support.Repositories; + +/** + * Interface for components that will populate the Spring Data repositories with objects. + * + * @author Oliver Gierke + * @since 1.4 + */ +public interface RepositoryPopulator { + + /** + * Populates the given {@link Repositories}. + * + * @param repositories + */ + void populate(Repositories repositories); +} diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/init/ResourceReader.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/init/ResourceReader.java new file mode 100644 index 000000000..acb892d7b --- /dev/null +++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/init/ResourceReader.java @@ -0,0 +1,30 @@ +/* + * Copyright 2012 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.repository.init; + +import org.springframework.core.io.Resource; + +/** + * @author Oliver Gierke + */ +public interface ResourceReader { + + public static enum Type { + XML, JSON; + } + + Object readFrom(Resource resource, ClassLoader classLoader) throws Exception; +} diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/init/ResourceReaderRepositoryPopulator.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/init/ResourceReaderRepositoryPopulator.java new file mode 100644 index 000000000..04285ef1f --- /dev/null +++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/init/ResourceReaderRepositoryPopulator.java @@ -0,0 +1,143 @@ +/* + * Copyright 2012 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.repository.init; + +import java.io.IOException; +import java.io.Serializable; +import java.util.Arrays; +import java.util.Collection; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.support.Repositories; +import org.springframework.util.Assert; + +/** + * A {@link RepositoryPopulator} using a {@link ResourceReader} to read objects from the configured {@link Resource} + * s. + * + * @author Oliver Gierke + * @since 1.4 + */ +public class ResourceReaderRepositoryPopulator implements RepositoryPopulator { + + private static final Log LOG = LogFactory.getLog(ResourceReaderRepositoryPopulator.class); + + private final ResourcePatternResolver resolver; + private final ResourceReader reader; + private final ClassLoader classLoader; + + private Collection resources; + + /** + * Creates a new {@link ResourceReaderRepositoryPopulator} using the given {@link ResourceReader}. + * + * @param reader must not be {@literal null}. + */ + public ResourceReaderRepositoryPopulator(ResourceReader reader) { + this(reader, null); + } + + /** + * Createsa a new {@link ResourceReaderRepositoryPopulator} using the given {@link ResourceReader} and + * {@link ClassLoader}. + * + * @param reader must not be {@literal null}. + * @param classLoader + */ + public ResourceReaderRepositoryPopulator(ResourceReader resourceReader, ClassLoader classLoader) { + + Assert.notNull(resourceReader); + + this.reader = resourceReader; + this.classLoader = classLoader; + this.resolver = classLoader == null ? new PathMatchingResourcePatternResolver() + : new PathMatchingResourcePatternResolver(classLoader); + } + + /** + * Configures the location of the {@link Resource}s to be used to initialize the repositories. + * + * @param location must not be {@literal null} or empty. + * @throws IOException + */ + public void setResourceLocation(String location) throws IOException { + Assert.hasText(location); + setResources(resolver.getResources(location)); + } + + /** + * Configures the {@link Resource}s to be used to initialize the repositories. + * + * @param resources + */ + public void setResources(Resource... resources) { + this.resources = Arrays.asList(resources); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.init.RepositoryPopulator#initialize() + */ + public void populate(Repositories repositories) { + + for (Resource resource : resources) { + + Object result = readObjectFrom(resource); + + if (result instanceof Collection) { + for (Object element : (Collection) result) { + if (element != null) { + persist(element, repositories); + } else { + LOG.info("Skipping null element found in unmarshal result!"); + } + } + } else { + persist(result, repositories); + } + } + } + + /** + * Reads the given resource into an {@link Object} using the configured {@link ResourceReader}. + * + * @param resource must not be {@literal null}. + * @return + */ + private Object readObjectFrom(Resource resource) { + try { + return reader.readFrom(resource, classLoader); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Persists the given {@link Object} using a suitable repository. + * + * @param object must not be {@literal null}. + * @param repositories must not be {@literal null}. + */ + private void persist(Object object, Repositories repositories) { + CrudRepository repository = repositories.getRepositoryFor(object.getClass()); + repository.save(object); + } +} diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/init/UnmarshallerRepositoryPopulatorFactoryBean.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/init/UnmarshallerRepositoryPopulatorFactoryBean.java new file mode 100644 index 000000000..0307c66d3 --- /dev/null +++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/init/UnmarshallerRepositoryPopulatorFactoryBean.java @@ -0,0 +1,57 @@ +/* + * Copyright 2012 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.repository.init; + +import org.springframework.beans.factory.FactoryBean; +import org.springframework.oxm.Unmarshaller; +import org.springframework.util.Assert; + +/** + * {@link FactoryBean} to create a {@link ResourceReaderRepositoryPopulator} using an {@link Unmarshaller}. + * + * @author Oliver Gierke + */ +public class UnmarshallerRepositoryPopulatorFactoryBean extends AbstractRepositoryPopulatorFactoryBean { + + private Unmarshaller unmarshaller; + + /** + * Configures the {@link Unmarshaller} to be used. + * + * @param unmarshaller the unmarshaller to set + */ + public void setUnmarshaller(Unmarshaller unmarshaller) { + this.unmarshaller = unmarshaller; + } + + /* (non-Javadoc) + * @see org.springframework.data.repository.init.AbstractRepositoryPopulatorFactoryBean#getResourceReader() + */ + @Override + protected ResourceReader getResourceReader() { + return new UnmarshallingResourceReader(unmarshaller); + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.config.AbstractFactoryBean#afterPropertiesSet() + */ + @Override + public void afterPropertiesSet() throws Exception { + Assert.notNull(unmarshaller, "No Unmarshaller configured!"); + super.afterPropertiesSet(); + } +} diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/init/UnmarshallingResourceReader.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/init/UnmarshallingResourceReader.java new file mode 100644 index 000000000..b6d8d3d2f --- /dev/null +++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/init/UnmarshallingResourceReader.java @@ -0,0 +1,47 @@ +/* + * Copyright 2012 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.repository.init; + +import java.io.IOException; + +import javax.xml.transform.stream.StreamSource; + +import org.springframework.core.io.Resource; +import org.springframework.oxm.Unmarshaller; + +/** + * @author Oliver Gierke + */ +public class UnmarshallingResourceReader implements ResourceReader { + + private final Unmarshaller unmarshaller; + + /** + * @param unmarshaller + */ + public UnmarshallingResourceReader(Unmarshaller unmarshaller) { + this.unmarshaller = unmarshaller; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.init.ResourceReader#readFrom(org.springframework.core.io.Resource, java.lang.ClassLoader) + */ + public Object readFrom(Resource resource, ClassLoader classLoader) throws IOException { + StreamSource source = new StreamSource(resource.getInputStream()); + return unmarshaller.unmarshal(source); + } +} diff --git a/spring-data-commons-core/src/main/resources/META-INF/spring.handlers b/spring-data-commons-core/src/main/resources/META-INF/spring.handlers new file mode 100644 index 000000000..685424493 --- /dev/null +++ b/spring-data-commons-core/src/main/resources/META-INF/spring.handlers @@ -0,0 +1 @@ +http\://www.springframework.org/schema/data/repository=org.springframework.data.repository.config.RepositoryNameSpaceHandler diff --git a/spring-data-commons-core/src/main/resources/META-INF/spring.schemas b/spring-data-commons-core/src/main/resources/META-INF/spring.schemas index 22d3f4bfa..95afeab10 100644 --- a/spring-data-commons-core/src/main/resources/META-INF/spring.schemas +++ b/spring-data-commons-core/src/main/resources/META-INF/spring.schemas @@ -1,2 +1,3 @@ http\://www.springframework.org/schema/data/repository/spring-repository-1.0.xsd=org/springframework/data/repository/config/spring-repository-1.0.xsd -http\://www.springframework.org/schema/data/repository/spring-repository.xsd=org/springframework/data/repository/config/spring-repository-1.0.xsd +http\://www.springframework.org/schema/data/repository/spring-repository-1.4.xsd=org/springframework/data/repository/config/spring-repository-1.4.xsd +http\://www.springframework.org/schema/data/repository/spring-repository.xsd=org/springframework/data/repository/config/spring-repository-1.4.xsd diff --git a/spring-data-commons-core/src/main/resources/META-INF/spring.tooling b/spring-data-commons-core/src/main/resources/META-INF/spring.tooling new file mode 100644 index 000000000..2edb997e7 --- /dev/null +++ b/spring-data-commons-core/src/main/resources/META-INF/spring.tooling @@ -0,0 +1,4 @@ +# Tooling related information for the repository namespace +http\://www.springframework.org/schema/data/repository@name=Repository Namespace +http\://www.springframework.org/schema/data/repository@prefix=repository +http\://www.springframework.org/schema/data/repository@icon=org/springframework/jdbc/config/spring-jdbc.gif diff --git a/spring-data-commons-core/src/main/resources/org/springframework/data/repository/config/spring-repository-1.4.xsd b/spring-data-commons-core/src/main/resources/org/springframework/data/repository/config/spring-repository-1.4.xsd new file mode 100644 index 000000000..785fb6d3a --- /dev/null +++ b/spring-data-commons-core/src/main/resources/org/springframework/data/repository/config/spring-repository-1.4.xsd @@ -0,0 +1,202 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Declares a single DAO instance. + + + + + + + + + + + + + + + + Where to find the files to read the objects from the repository shall be populated with. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-data-commons-core/src/test/java/org/springframework/data/repository/config/ResourceReaderRepositoryPopulatorBeanDefinitionParserIntegrationTests.java b/spring-data-commons-core/src/test/java/org/springframework/data/repository/config/ResourceReaderRepositoryPopulatorBeanDefinitionParserIntegrationTests.java new file mode 100644 index 000000000..9faf6706c --- /dev/null +++ b/spring-data-commons-core/src/test/java/org/springframework/data/repository/config/ResourceReaderRepositoryPopulatorBeanDefinitionParserIntegrationTests.java @@ -0,0 +1,78 @@ +/* + * Copyright 2012 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.repository.config; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +import org.junit.Test; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; +import org.springframework.core.io.ClassPathResource; +import org.springframework.data.repository.init.JacksonResourceReader; +import org.springframework.data.repository.init.ResourceReaderRepositoryPopulator; +import org.springframework.data.repository.init.UnmarshallingResourceReader; +import org.springframework.oxm.jaxb.Jaxb2Marshaller; +import org.springframework.test.util.ReflectionTestUtils; + +/** + * Integratin tests for the initializer namespace elements. + * + * @author Oliver Gierke + */ +public class ResourceReaderRepositoryPopulatorBeanDefinitionParserIntegrationTests { + + /** + * @see DATACMNS-58 + */ + @Test + public void registersJacksonInitializerCorrectly() { + + DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory); + reader.loadBeanDefinitions(new ClassPathResource("populators.xml", getClass())); + + BeanDefinition definition = beanFactory.getBeanDefinition("jackson-populator"); + assertThat(definition, is(notNullValue())); + + Object bean = beanFactory.getBean("jackson-populator"); + assertThat(bean, is(instanceOf(ResourceReaderRepositoryPopulator.class))); + Object resourceReader = ReflectionTestUtils.getField(bean, "reader"); + assertThat(resourceReader, is(instanceOf(JacksonResourceReader.class))); + } + + /** + * @see DATACMNS-58 + */ + @Test + public void registersXmlInitializerCorrectly() { + + DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory); + reader.loadBeanDefinitions(new ClassPathResource("populators.xml", getClass())); + + BeanDefinition definition = beanFactory.getBeanDefinition("xml-populator"); + assertThat(definition, is(notNullValue())); + + Object bean = beanFactory.getBean("xml-populator"); + assertThat(bean, is(instanceOf(ResourceReaderRepositoryPopulator.class))); + Object resourceReader = ReflectionTestUtils.getField(bean, "reader"); + assertThat(resourceReader, is(instanceOf(UnmarshallingResourceReader.class))); + Object unmarshaller = ReflectionTestUtils.getField(resourceReader, "unmarshaller"); + assertThat(unmarshaller, is(instanceOf(Jaxb2Marshaller.class))); + } +} diff --git a/spring-data-commons-core/src/test/java/org/springframework/data/repository/init/JacksonResourceReaderIntegrationTests.java b/spring-data-commons-core/src/test/java/org/springframework/data/repository/init/JacksonResourceReaderIntegrationTests.java new file mode 100644 index 000000000..55c36c183 --- /dev/null +++ b/spring-data-commons-core/src/test/java/org/springframework/data/repository/init/JacksonResourceReaderIntegrationTests.java @@ -0,0 +1,40 @@ +/* + * Copyright 2012 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.repository.init; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import java.util.Collection; + +import org.junit.Test; +import org.springframework.core.io.ClassPathResource; + +/** + * @author Oliver Gierke + */ +public class JacksonResourceReaderIntegrationTests { + + @Test + public void readsFileWithMultipleObjects() throws Exception { + + ResourceReader reader = new JacksonResourceReader(); + Object result = reader.readFrom(new ClassPathResource("data.json", getClass()), null); + + assertThat(result, is(instanceOf(Collection.class))); + assertThat((Collection) result, hasSize(1)); + } +} diff --git a/spring-data-commons-core/src/test/java/org/springframework/data/repository/init/Person.java b/spring-data-commons-core/src/test/java/org/springframework/data/repository/init/Person.java new file mode 100644 index 000000000..b17d8afd9 --- /dev/null +++ b/spring-data-commons-core/src/test/java/org/springframework/data/repository/init/Person.java @@ -0,0 +1,25 @@ +/* + * Copyright 2012 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.repository.init; + +/** + * @author Oliver Gierke + */ +public class Person { + + String firstname; + String lastname; +} diff --git a/spring-data-commons-core/src/test/java/org/springframework/data/repository/init/ResourceReaderRepositoryInitializerUnitTests.java b/spring-data-commons-core/src/test/java/org/springframework/data/repository/init/ResourceReaderRepositoryInitializerUnitTests.java new file mode 100644 index 000000000..0bea8b790 --- /dev/null +++ b/spring-data-commons-core/src/test/java/org/springframework/data/repository/init/ResourceReaderRepositoryInitializerUnitTests.java @@ -0,0 +1,80 @@ +/* + * Copyright 2012 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.repository.init; + +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Collections; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.core.io.Resource; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.support.Repositories; + +/** + * Unit tests for {@link UnmarshallingRepositoryInitializer}. + * + * @author Oliver Gierke + */ +@RunWith(MockitoJUnitRunner.class) +public class ResourceReaderRepositoryInitializerUnitTests { + + @Mock + ResourceReader reader; + @Mock + Repositories repositories; + @Mock + Resource resource; + @Mock + CrudRepository repo; + + @Test + public void storesSingleObjectCorrectly() throws Exception { + + Object reference = new Object(); + + setUpReferenceAndInititalize(reference); + + verify(repo, times(1)).save(reference); + } + + @Test + public void storesCollectionOfObjectsCorrectly() throws Exception { + + Object object = new Object(); + Collection reference = Collections.singletonList(object); + + setUpReferenceAndInititalize(reference); + + verify(repo, times(1)).save(object); + } + + private void setUpReferenceAndInititalize(Object reference) throws Exception { + + when(reader.readFrom(any(Resource.class), any(ClassLoader.class))).thenReturn(reference); + when(repositories.getRepositoryFor(Object.class)).thenReturn(repo); + + ResourceReaderRepositoryPopulator initializer = new ResourceReaderRepositoryPopulator(reader); + initializer.setResources(resource); + initializer.populate(repositories); + } +} diff --git a/spring-data-commons-core/src/test/resources/org/springframework/data/repository/config/jaxb.index b/spring-data-commons-core/src/test/resources/org/springframework/data/repository/config/jaxb.index new file mode 100644 index 000000000..e69de29bb diff --git a/spring-data-commons-core/src/test/resources/org/springframework/data/repository/config/populators.xml b/spring-data-commons-core/src/test/resources/org/springframework/data/repository/config/populators.xml new file mode 100644 index 000000000..62643d683 --- /dev/null +++ b/spring-data-commons-core/src/test/resources/org/springframework/data/repository/config/populators.xml @@ -0,0 +1,18 @@ + + + + + + + + + + diff --git a/spring-data-commons-core/src/test/resources/org/springframework/data/repository/init/data.json b/spring-data-commons-core/src/test/resources/org/springframework/data/repository/init/data.json new file mode 100644 index 000000000..5f977afbd --- /dev/null +++ b/spring-data-commons-core/src/test/resources/org/springframework/data/repository/init/data.json @@ -0,0 +1,3 @@ +[ { "_class" : "org.springframework.data.repository.init.Person", + "firstname" : "Dave", + "lastname" : "Matthews" } ] \ No newline at end of file diff --git a/spring-data-commons-core/template.mf b/spring-data-commons-core/template.mf index d2332be3f..166033ad7 100644 --- a/spring-data-commons-core/template.mf +++ b/spring-data-commons-core/template.mf @@ -8,16 +8,19 @@ Import-Template: com.mysema.query.*;version="[2.2.0,3.0.0)";resolution:=optional, javax.enterprise.*;version="${cdi.version:[=.=.=,+1.0.0)}";resolution:=optional, javax.inject.*;version="[1.0.0,2.0.0)";resolution:=optional, + javax.xml.transform.*;version="0";resolution:=optional, + org.codehaus.jackson.*;version="${jackson.version:[=.=.=,+1.0.0)}";resolution:=optional, org.springframework.aop.*;version="${org.springframework.version.30:[=.=.=,+1.0.0)}";resolution:=optional, org.springframework.beans.*;version="${org.springframework.version.30:[=.=.=,+1.0.0)}", org.springframework.core.*;version="${org.springframework.version.30:[=.=.=,+1.0.0)}", org.springframework.context.*;version="${org.springframework.version.30:[=.=.=,+1.0.0)}";resolution:=optional, org.springframework.dao.*;version="${org.springframework.version.30:[=.=.=,+1.0.0)}";resolution:=optional, - org.springframework.util.*;version="${org.springframework.version.30:[=.=.=,+1.0.0)}", org.springframework.expression.*;version="${org.springframework.version.30:[=.=.=,+1.0.0)}";resolution:=optional, org.springframework.expression.spel.standard.*;version="${org.springframework.version.30:[=.=.=,+1.0.0)}";resolution:=optional, org.springframework.expression.spel.support.*;version="${org.springframework.version.30:[=.=.=,+1.0.0)}";resolution:=optional, + org.springframework.oxm.*;version="${org.springframework.version.30:[=.=.=,+1.0.0)}";resolution:=optional, org.springframework.transaction.*;version="${org.springframework.version.30:[=.=.=,+1.0.0)}";resolution:=optional, + org.springframework.util.*;version="${org.springframework.version.30:[=.=.=,+1.0.0)}", org.springframework.validation.*;version="${org.springframework.version.30:[=.=.=,+1.0.0)}";resolution:=optional, org.springframework.web.*;version="${org.springframework.version.30:[=.=.=,+1.0.0)}";resolution:=optional, org.aopalliance.*;version="[1.0.0, 2.0.0)";resolution:=optional, diff --git a/src/docbkx/repositories.xml b/src/docbkx/repositories.xml index 4de861e7f..a3571503a 100644 --- a/src/docbkx/repositories.xml +++ b/src/docbkx/repositories.xml @@ -1028,5 +1028,88 @@ public class UserController { + +
+ Repository populators + + If you have been working with the JDBC module of Spring you're + probably familiar with the support to populate a DataSource using SQL + scripts. A similar abstraction is available on the repositories level + although we don't use SQL as data definition language as we need to be + store independent of course. Thus the populators support XML (through + Spring's OXM abstraction) and JSON (through Jackson) to define data for + the repositories to be populated with. + + Assume you have a file data.json with the + following content: + + + Data defined in JSON + + [ { "_class" : "com.acme.Person", + "firstname" : "Dave", + "lastname" : "Matthews" }, + { "_class" : "com.acme.Person", + "firstname" : "Carter", + "lastname" : "Beauford" } ] + + + You can easily populate you repositories by using the populator + elements of the repository namespace provided in Spring Data Commons. To + get the just shown data be populated to your + PersonRepository all you need to do is + the following: + + + Declaring a Jackson repository populator + + <?xml version="1.0" encoding="UTF-8"?> +<beans xmlns="http://www.springframework.org/schema/beans" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:repository="http://www.springframework.org/schema/data/repository" + xsi:schemaLocation="http://www.springframework.org/schema/beans + http://www.springframework.org/schema/beans/spring-beans.xsd + http://www.springframework.org/schema/data/repository + http://www.springframework.org/schema/data/repository/spring-repository.xsd"> + + <repository:jackson-populator location="classpath:data.json" /> + +</beans> + + + This declaration causes the data.json file being read, + deserialized by a Jackson ObjectMapper. The type + the JSON object will be unmarshalled to will be determined by inspecting + the _class attribute of the JSON document. We will + eventually select the appropriate repository being able to handle the + object just deserialized. + + To rather use XML to define the repositories shall be populated + with you can use the unmarshaller-populator you hand one of the + marshaller options Spring OXM provides you with. + + + Declaring an unmarshalling repository populator (using + JAXB) + + <?xml version="1.0" encoding="UTF-8"?> +<beans xmlns="http://www.springframework.org/schema/beans" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:repository="http://www.springframework.org/schema/data/repository" + xmlns:oxm="http://www.springframework.org/schema/oxm" + xsi:schemaLocation="http://www.springframework.org/schema/beans + http://www.springframework.org/schema/beans/spring-beans.xsd + http://www.springframework.org/schema/data/repository + http://www.springframework.org/schema/data/repository/spring-repository.xsd + http://www.springframework.org/schema/oxm + http://www.springframework.org/schema/oxm/spring-oxm.xsd"> + + <repository:unmarshaller-populator location="classpath:data.json" unmarshaller-ref="unmarshaller" /> + + <oxm:jaxb2-marshaller contextPath="com.acme" /> + +</beans> + +