DATACMNS-58 - Added support for repository populators.

Added RepositoryPopulator abstraction and implementations based on Spring OXM Unmarshallers as well as Jackson. This allows arbitrary repositories being populated with data pulled from XML / JSON, no matter what store they are actually backed. The populator will eventually populate the repositories held in a Repositories instance. It can be used like this:

Repositories repositories = new Repositories(applicationContext);
ResourceReader reader = new JacksonResourceReader();

ResourceReaderRepositoryPopulator populator = new ResourceReaderRepositoryPopulator(reader);
populator.setResourceLocation("classpath*:data.json");
populator.populate(repositories);

The ResourceReader defines what technology shall be used to read the data from the file into objects. The ResourceReaderRepositoryPopulator uses the reader and can either get a set of Resource instances configured or is able to lookup resources using a location string. The actual Repositories instance captures all CrudRepository instances contained inside an ApplicationContext.

The populators can also be used from within XML configuration though the repository namespace elements shown below:

<repository:jackson-populator location="classpath:org/springframework/data/repository/init/data.json" />
		
<repository:unmarshaller-populator location="classpath:org/springframework/data/repository/init/data.xml" unmarshaller-ref="unmarshaller" />

Updated reference documentation accordingly.
This commit is contained in:
Oliver Gierke
2012-06-21 19:32:07 +02:00
parent 149cfa068c
commit d441d95197
24 changed files with 1248 additions and 2 deletions

View File

@@ -16,6 +16,7 @@
<querydsl.version>2.5.0</querydsl.version>
<cdi.version>1.0</cdi.version>
<webbeans.version>1.1.3</webbeans.version>
<jackson.version>1.9.7</jackson.version>
</properties>
<dependencies>
@@ -32,6 +33,18 @@
<version>${org.springframework.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-oxm</artifactId>
<version>${org.springframework.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>${jackson.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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<ResourceReaderRepositoryPopulator> implements ApplicationListener<ContextRefreshedEvent> {
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();
}

View File

@@ -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);
}
}

View File

@@ -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<JsonNode> elements = node.getElements();
List<Object> result = new ArrayList<Object>();
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);
}
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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<Resource> 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<Object, Serializable> repository = repositories.getRepositoryFor(object.getClass());
repository.save(object);
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1 @@
http\://www.springframework.org/schema/data/repository=org.springframework.data.repository.config.RepositoryNameSpaceHandler

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,202 @@
<?xml version="1.0" encoding="UTF-8" ?>
<xsd:schema xmlns="http://www.springframework.org/schema/data/repository"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:tool="http://www.springframework.org/schema/tool"
xmlns:context="http://www.springframework.org/schema/context"
targetNamespace="http://www.springframework.org/schema/data/repository"
elementFormDefault="qualified" attributeFormDefault="unqualified">
<xsd:import namespace="http://www.springframework.org/schema/tool" />
<xsd:import namespace="http://www.springframework.org/schema/context"
schemaLocation="http://www.springframework.org/schema/context/spring-context.xsd" />
<xsd:complexType name="repositories">
<xsd:sequence>
<xsd:element name="include-filter" type="context:filterType"
minOccurs="0" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation><![CDATA[
Controls which eligible types to include for component scanning.
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="exclude-filter" type="context:filterType" minOccurs="0" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation><![CDATA[
Controls which eligible types to exclude for component scanning.
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="base-package" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation><![CDATA[
Defines the base package where the DAO interface will be tried to be detected.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="named-queries-location" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Defines the location to look for a Properties file containing externally defined queries.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:complexType name="repository">
<xsd:annotation>
<xsd:documentation>
Declares a single DAO instance.
</xsd:documentation>
<xsd:appinfo>
<tool:annotation>
<tool:exports identifier="@id"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:attribute name="id" type="xsd:string" use="required"/>
<xsd:attribute name="custom-impl-ref" type="customImplementationReference"/>
</xsd:complexType>
<xsd:complexType name="populator">
<xsd:attribute name="location" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
Where to find the files to read the objects from the repository shall be populated with.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="id" type="xsd:string" />
</xsd:complexType>
<!-- XML (Unmarshaller) initializer -->
<xsd:element name="unmarshaller-populator">
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="populator">
<xsd:attribute name="unmarshaller-ref" type="unmarshallerRefType" use="required" />
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:simpleType name="unmarshallerRefType">
<xsd:annotation>
<xsd:appinfo>
<tool:expected-type type="org.springframework.oxm.Unmarshaller" />
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string" />
</xsd:simpleType>
<!-- JSON (Jackson) initializer -->
<xsd:element name="jackson-populator">
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="populator">
<xsd:attribute name="object-mapper-ref" type="objectMapperType" />
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:simpleType name="objectMapperType">
<xsd:annotation>
<xsd:appinfo>
<tool:expected-type type="org.codehaus.jackson.map.ObjectMapper" />
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string" />
</xsd:simpleType>
<xsd:attributeGroup name="repository-attributes">
<xsd:attribute name="repository-impl-postfix" type="xsd:string"/>
<xsd:attribute name="query-lookup-strategy" type="query-strategy"/>
<xsd:attribute name="factory-class" type="classType"/>
</xsd:attributeGroup>
<xsd:attributeGroup name="transactional-repository-attributes">
<xsd:attributeGroup ref="repository-attributes"/>
<xsd:attribute name="transaction-manager-ref" type="transactionManagerRef"/>
</xsd:attributeGroup>
<xsd:simpleType name="query-strategy">
<xsd:annotation>
<xsd:documentation><![CDATA[
Determines the way query methods are being executed.
]]></xsd:documentation>
</xsd:annotation>
<xsd:restriction base="xsd:string">
<xsd:enumeration value="create-if-not-found">
<xsd:annotation>
<xsd:documentation><![CDATA[
Tries to find a named query but creates a custom query if
none can be found. (Default)
]]></xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="create">
<xsd:annotation>
<xsd:documentation><![CDATA[
Creates a query from the query method's name.
]]></xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="use-declared-query">
<xsd:annotation>
<xsd:documentation><![CDATA[
Uses a declared query to execute. Fails if no
declared query (either through named query or through @Query)
is defined.
]]></xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="customImplementationReference">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref"/>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string"/>
</xsd:simpleType>
<xsd:simpleType name="entityManagerFactoryRef">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="org.springframework.orm.jpa.AbstractEntityManagerFactoryBean"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string"/>
</xsd:simpleType>
<xsd:simpleType name="transactionManagerRef">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="org.springframework.transaction.PlatformTransactionManager"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string"/>
</xsd:simpleType>
<xsd:simpleType name="classType">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="direct">
<tool:expected-type type="java.lang.Class"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string"/>
</xsd:simpleType>
</xsd:schema>

View File

@@ -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)));
}
}

View File

@@ -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));
}
}

View File

@@ -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;
}

View File

@@ -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<Object, Serializable> 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<Object> 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);
}
}

View File

@@ -0,0 +1,18 @@
<?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/oxm http://www.springframework.org/schema/oxm/spring-oxm-3.1.xsd
http://www.springframework.org/schema/data/repository http://www.springframework.org/schema/data/repository/spring-repository.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<repository:jackson-populator id="jackson-populator"
location="classpath:org/springframework/data/repository/init/data.json" />
<repository:unmarshaller-populator
id="xml-populator" location="classpath:org/springframework/data/repository/init/data.xml"
unmarshaller-ref="unmarshaller" />
<oxm:jaxb2-marshaller id="unmarshaller" contextPath="org.springframework.data.repository.config" />
</beans>

View File

@@ -0,0 +1,3 @@
[ { "_class" : "org.springframework.data.repository.init.Person",
"firstname" : "Dave",
"lastname" : "Matthews" } ]

View File

@@ -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,

View File

@@ -1028,5 +1028,88 @@ public class UserController {
</programlisting>
</simplesect>
</section>
<section>
<title>Repository populators</title>
<para>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.</para>
<para>Assume you have a file <filename>data.json</filename> with the
following content:</para>
<example>
<title>Data defined in JSON</title>
<programlisting language="javascript">[ { "_class" : "com.acme.Person",
"firstname" : "Dave",
"lastname" : "Matthews" },
{ "_class" : "com.acme.Person",
"firstname" : "Carter",
"lastname" : "Beauford" } ]</programlisting>
</example>
<para>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
<interfacename>PersonRepository</interfacename> all you need to do is
the following:</para>
<example>
<title>Declaring a Jackson repository populator</title>
<programlisting language="xml">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;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"&gt;
&lt;repository:jackson-populator location="classpath:data.json" /&gt;
&lt;/beans&gt;</programlisting>
</example>
<para>This declaration causes the data.json file being read,
deserialized by a Jackson <classname>ObjectMapper</classname>. The type
the JSON object will be unmarshalled to will be determined by inspecting
the <code>_class</code> attribute of the JSON document. We will
eventually select the appropriate repository being able to handle the
object just deserialized.</para>
<para>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.</para>
<example>
<title>Declaring an unmarshalling repository populator (using
JAXB)</title>
<programlisting language="xml">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;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"&gt;
&lt;repository:unmarshaller-populator location="classpath:data.json" unmarshaller-ref="unmarshaller" /&gt;
&lt;oxm:jaxb2-marshaller contextPath="com.acme" /&gt;
&lt;/beans&gt;</programlisting>
</example>
</section>
</section>
</chapter>