diff --git a/spring-data-commons-core/pom.xml b/spring-data-commons-core/pom.xml
index d03a3c6e3..5e825ae0c 100644
--- a/spring-data-commons-core/pom.xml
+++ b/spring-data-commons-core/pom.xml
@@ -77,6 +77,13 @@
junitjunit
+
+
+ joda-time
+ joda-time
+ 1.6
+ true
+
diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/domain/Auditable.java b/spring-data-commons-core/src/main/java/org/springframework/data/domain/Auditable.java
new file mode 100644
index 000000000..fe9f37d17
--- /dev/null
+++ b/spring-data-commons-core/src/main/java/org/springframework/data/domain/Auditable.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2008-2010 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.domain;
+
+import java.io.Serializable;
+
+import org.joda.time.DateTime;
+
+
+/**
+ * Interface for auditable entities. Allows storing and retrieving creation and
+ * modification information. The changing instance (typically some user) is to
+ * be defined by a generics definition.
+ *
+ * @author Oliver Gierke
+ * @param the auditing type. Typically some kind of user.
+ * @param the type of the auditing type's idenifier
+ */
+public interface Auditable extends Persistable {
+
+ /**
+ * Returns the user who created this entity.
+ *
+ * @return the createdBy
+ */
+ U getCreatedBy();
+
+
+ /**
+ * Sets the user who created this entity.
+ *
+ * @param createdBy the creating entity to set
+ */
+ void setCreatedBy(final U createdBy);
+
+
+ /**
+ * Returns the creation date of the entity.
+ *
+ * @return the createdDate
+ */
+ DateTime getCreatedDate();
+
+
+ /**
+ * Sets the creation date of the entity.
+ *
+ * @param creationDate the creation date to set
+ */
+ void setCreatedDate(final DateTime creationDate);
+
+
+ /**
+ * Returns the user who modified the entity lastly.
+ *
+ * @return the lastModifiedBy
+ */
+ U getLastModifiedBy();
+
+
+ /**
+ * Sets the user who modified the entity lastly.
+ *
+ * @param lastModifiedBy the last modifying entity to set
+ */
+ void setLastModifiedBy(final U lastModifiedBy);
+
+
+ /**
+ * Returns the date of the last modification.
+ *
+ * @return the lastModifiedDate
+ */
+ DateTime getLastModifiedDate();
+
+
+ /**
+ * Sets the date of the last modification.
+ *
+ * @param lastModifiedDate the date of the last modification to set
+ */
+ void setLastModifiedDate(final DateTime lastModifiedDate);
+}
diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/domain/AuditorAware.java b/spring-data-commons-core/src/main/java/org/springframework/data/domain/AuditorAware.java
new file mode 100644
index 000000000..e8b6dedb3
--- /dev/null
+++ b/spring-data-commons-core/src/main/java/org/springframework/data/domain/AuditorAware.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2008-2010 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.domain;
+
+/**
+ * Interface for components that are aware of the application's current auditor.
+ * This will be some kind of user mostly.
+ *
+ * @author Oliver Gierke
+ * @param the type of the auditing instance
+ */
+public interface AuditorAware {
+
+ /**
+ * Returns the current auditor of the application.
+ *
+ * @return the current auditor
+ */
+ T getCurrentAuditor();
+}
diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/domain/Page.java b/spring-data-commons-core/src/main/java/org/springframework/data/domain/Page.java
new file mode 100644
index 000000000..822e5e025
--- /dev/null
+++ b/spring-data-commons-core/src/main/java/org/springframework/data/domain/Page.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2008-2010 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.domain;
+
+import java.util.Iterator;
+import java.util.List;
+
+
+/**
+ * A page is a sublist of a list of objects. It allows gain information about
+ * the position of it in the containing entire list.
+ *
+ * @author Oliver Gierke
+ * @param
+ */
+public interface Page extends Iterable {
+
+ /**
+ * Returns the number of the current page. Is always greater than zero and
+ * less that {@code Page#getTotalPages()}.
+ *
+ * @return the number of the current page
+ */
+ int getNumber();
+
+
+ /**
+ * Returns the size of the page.
+ *
+ * @return the size of the page
+ */
+ int getSize();
+
+
+ /**
+ * Returns the number of total pages.
+ *
+ * @return the number of toral pages
+ */
+ int getTotalPages();
+
+
+ /**
+ * Returns the number of elements currently on this page.
+ *
+ * @return the number of elements currently on this page
+ */
+ int getNumberOfElements();
+
+
+ /**
+ * Returns the total amount of elements.
+ *
+ * @return the total amount of elements
+ */
+ long getTotalElements();
+
+
+ /**
+ * Returns if there is a previous page.
+ *
+ * @return if there is a previous page
+ */
+ boolean hasPreviousPage();
+
+
+ /**
+ * Returns whether the current page is the first one.
+ *
+ * @return
+ */
+ boolean isFirstPage();
+
+
+ /**
+ * Returns if there is a next page.
+ *
+ * @return if there is a next page
+ */
+ boolean hasNextPage();
+
+
+ /**
+ * Returns whether the current page is the last one.
+ *
+ * @return
+ */
+ boolean isLastPage();
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Iterable#iterator()
+ */
+ Iterator iterator();
+
+
+ /**
+ * Returns the page content as {@link List}.
+ *
+ * @return
+ */
+ List getContent();
+
+
+ /**
+ * Returns the sorting parameters for the page.
+ *
+ * @return
+ */
+ Sort getSort();
+}
\ No newline at end of file
diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/domain/PageImpl.java b/spring-data-commons-core/src/main/java/org/springframework/data/domain/PageImpl.java
new file mode 100644
index 000000000..2d58a539f
--- /dev/null
+++ b/spring-data-commons-core/src/main/java/org/springframework/data/domain/PageImpl.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright 2008-2010 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.domain;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+
+/**
+ * Basic {@code Page} implementation.
+ *
+ * @author Oliver Gierke
+ * @param the type of which the page consists.
+ */
+public class PageImpl implements Page {
+
+ private final List content = new ArrayList();
+ private final Pageable pageable;
+ private final long total;
+
+
+ /**
+ * Constructor of {@code PageImpl}.
+ *
+ * @param content the content of this page
+ * @param pageable the paging information
+ * @param total the total amount of items available
+ */
+ public PageImpl(List content, Pageable pageable, long total) {
+
+ if (null == content) {
+ throw new IllegalArgumentException("Content must not be null!");
+ }
+
+ this.content.addAll(content);
+ this.total = total;
+
+ this.pageable =
+ null == pageable ? new PageRequest(0, content.size())
+ : pageable;
+ }
+
+
+ /**
+ * Creates a new {@link PageImpl} with the given content. This will result
+ * in the created {@link Page} being identical to the entire {@link List}.
+ *
+ * @param content
+ */
+ public PageImpl(List content) {
+
+ this(content, null, (null == content) ? 0 : content.size());
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.springframework.data.domain.Page#getNumber()
+ */
+ public int getNumber() {
+
+ return pageable.getPageNumber();
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.springframework.data.domain.Page#getSize()
+ */
+ public int getSize() {
+
+ return pageable.getPageSize();
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.springframework.data.domain.Page#getTotalPages()
+ */
+ public int getTotalPages() {
+
+ return (int) Math.ceil((double) total / (double) getSize());
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.springframework.data.domain.Page#getNumberOfElements()
+ */
+ public int getNumberOfElements() {
+
+ return content.size();
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.springframework.data.domain.Page#getTotalElements()
+ */
+ public long getTotalElements() {
+
+ return total;
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.springframework.data.domain.Page#hasPreviousPage()
+ */
+ public boolean hasPreviousPage() {
+
+ return getNumber() > 0;
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.springframework.data.domain.Page#isFirstPage()
+ */
+ public boolean isFirstPage() {
+
+ return !hasPreviousPage();
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.springframework.data.domain.Page#hasNextPage()
+ */
+ public boolean hasNextPage() {
+
+ return ((getNumber() + 1) * getSize()) < total;
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.springframework.data.domain.Page#isLastPage()
+ */
+ public boolean isLastPage() {
+
+ return !hasNextPage();
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.springframework.data.domain.Page#iterator()
+ */
+ public Iterator iterator() {
+
+ return content.iterator();
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.springframework.data.domain.Page#asList()
+ */
+ public List getContent() {
+
+ return Collections.unmodifiableList(content);
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.springframework.data.domain.Page#getSort()
+ */
+ public Sort getSort() {
+
+ return pageable.getSort();
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+
+ String contentType = "UNKNOWN";
+
+ if (content.size() > 0) {
+ contentType = content.get(0).getClass().getName();
+ }
+
+ return String.format("Page %s of %d containing %s instances",
+ getNumber(), getTotalPages(), contentType);
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object obj) {
+
+ if (this == obj) {
+ return true;
+ }
+
+ if (!(obj instanceof PageImpl>)) {
+ return false;
+ }
+
+ PageImpl> that = (PageImpl>) obj;
+
+ boolean totalEqual = this.total == that.total;
+ boolean contentEqual = this.content.equals(that.content);
+ boolean pageableEqual = this.pageable.equals(that.pageable);
+
+ return totalEqual && contentEqual && pageableEqual;
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+
+ int result = 17;
+
+ result = 31 * result + (int) (total ^ total >>> 32);
+ result = 31 * result + pageable.hashCode();
+ result = 31 * result + content.hashCode();
+
+ return result;
+ }
+}
diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/domain/PageRequest.java b/spring-data-commons-core/src/main/java/org/springframework/data/domain/PageRequest.java
new file mode 100644
index 000000000..431699130
--- /dev/null
+++ b/spring-data-commons-core/src/main/java/org/springframework/data/domain/PageRequest.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2008-2010 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.domain;
+
+import org.springframework.data.domain.Sort.Direction;
+
+
+/**
+ * Basic Java Bean implementation of {@code Pageable}.
+ *
+ * @author Oliver Gierke
+ */
+public class PageRequest implements Pageable {
+
+ private final int page;
+ private final int size;
+ private final Sort sort;
+
+
+ /**
+ * Creates a new {@link PageRequest}. Pages are zero indexed, thus providing
+ * 0 for {@code page} will return the first page.
+ *
+ * @param size
+ * @param page
+ */
+ public PageRequest(int page, int size) {
+
+ this(page, size, null);
+ }
+
+
+ /**
+ * Creates a new {@link PageRequest} with sort parameters applied.
+ *
+ * @param page
+ * @param size
+ * @param direction
+ * @param properties
+ */
+ public PageRequest(int page, int size, Direction direction,
+ String... properties) {
+
+ this(page, size, new Sort(direction, properties));
+ }
+
+
+ /**
+ * Creates a new {@link PageRequest} with sort parameters applied.
+ *
+ * @param page
+ * @param size
+ * @param sort
+ */
+ public PageRequest(int page, int size, Sort sort) {
+
+ if (0 > page) {
+ throw new IllegalArgumentException(
+ "Page index must not be less than zero!");
+ }
+
+ if (0 > size) {
+ throw new IllegalArgumentException(
+ "Page size must not be less than or equal to zero!");
+ }
+
+ this.page = page;
+ this.size = size;
+ this.sort = sort;
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.springframework.data.domain.Pageable#getPageSize()
+ */
+ public int getPageSize() {
+
+ return size;
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.springframework.data.domain.Pageable#getPageNumber()
+ */
+ public int getPageNumber() {
+
+ return page;
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.springframework.data.domain.Pageable#getFirstItem()
+ */
+ public int getFirstItem() {
+
+ return page * size;
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.springframework.data.domain.Pageable#getSort()
+ */
+ public Sort getSort() {
+
+ return sort;
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(final Object obj) {
+
+ if (this == obj) {
+ return true;
+ }
+
+ if (!(obj instanceof PageRequest)) {
+ return false;
+ }
+
+ PageRequest that = (PageRequest) obj;
+
+ boolean pageEqual = this.page == that.page;
+ boolean sizeEqual = this.size == that.size;
+
+ boolean sortEqual =
+ this.sort == null ? that.sort == null : this.sort
+ .equals(that.sort);
+
+ return pageEqual && sizeEqual && sortEqual;
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+
+ int result = 17;
+
+ result = 31 * result + page;
+ result = 31 * result + size;
+ result = 31 * result + (null == sort ? 0 : sort.hashCode());
+
+ return result;
+ }
+}
diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/domain/Pageable.java b/spring-data-commons-core/src/main/java/org/springframework/data/domain/Pageable.java
new file mode 100644
index 000000000..6498c6637
--- /dev/null
+++ b/spring-data-commons-core/src/main/java/org/springframework/data/domain/Pageable.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2008-2010 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.domain;
+
+/**
+ * Abstract interface for pagination information.
+ *
+ * @author Oliver Gierke
+ */
+public interface Pageable {
+
+ /**
+ * Returns the page to be returned.
+ *
+ * @return the page to be returned.
+ */
+ int getPageNumber();
+
+
+ /**
+ * Returns the number of items to be returned.
+ *
+ * @return the number of items of that page
+ */
+ int getPageSize();
+
+
+ /**
+ * Returns the first item relatively to the total number of items.
+ *
+ * @return the first item to be returned
+ */
+ int getFirstItem();
+
+
+ /**
+ * Returns the sorting parameters.
+ *
+ * @return
+ */
+ Sort getSort();
+}
diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/domain/Persistable.java b/spring-data-commons-core/src/main/java/org/springframework/data/domain/Persistable.java
new file mode 100644
index 000000000..69c587a75
--- /dev/null
+++ b/spring-data-commons-core/src/main/java/org/springframework/data/domain/Persistable.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2008-2010 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.domain;
+
+import java.io.Serializable;
+
+
+/**
+ * Simple interface for entities.
+ *
+ * @author Oliver Gierke
+ * @param the type of the identifier
+ */
+public interface Persistable extends Serializable {
+
+ /**
+ * Returns the id of the entity.
+ *
+ * @return the id
+ */
+ PK getId();
+
+
+ /**
+ * Returns if the {@code Persistable} is new or was persisted already.
+ *
+ * @return if the object is new
+ */
+ boolean isNew();
+}
diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/domain/Sort.java b/spring-data-commons-core/src/main/java/org/springframework/data/domain/Sort.java
new file mode 100644
index 000000000..0060b145f
--- /dev/null
+++ b/spring-data-commons-core/src/main/java/org/springframework/data/domain/Sort.java
@@ -0,0 +1,364 @@
+/*
+ * Copyright 2008-2010 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.domain;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+
+import org.springframework.util.StringUtils;
+
+
+/**
+ * Sort option for queries. You have to provide at least a list of properties to
+ * sort for that must not include {@code null} or empty strings. The direction
+ * defaults to {@value Sort#DEFAULT_DIRECTION}.
+ *
+ * @author Oliver Gierke
+ */
+public class Sort implements
+ Iterable {
+
+ public static final Direction DEFAULT_DIRECTION = Direction.ASC;
+
+ private List orders;
+
+
+ public Sort(Order... orders) {
+
+ this(Arrays.asList(orders));
+ }
+
+
+ /**
+ * Creates a new {@link Sort} instance.
+ *
+ * @param orders must not be {@literal null} or contain {@literal null} or
+ * empty strings
+ */
+ public Sort(List orders) {
+
+ if (null == orders || orders.isEmpty()) {
+ throw new IllegalArgumentException(
+ "You have to provide at least one sort property to sort by!");
+ }
+
+ this.orders = orders;
+ }
+
+
+ /**
+ * Creates a new {@link Sort} instance. Order defaults to
+ * {@value Direction#ASC}.
+ *
+ * @param properties must not be {@literal null} or contain {@literal null}
+ * or empty strings
+ */
+ public Sort(String... properties) {
+
+ this(DEFAULT_DIRECTION, properties);
+ }
+
+
+ /**
+ * Creates a new {@link Sort} instance.
+ *
+ * @param direction defaults to {@value Sort#DEFAULT_DIRECTION} (for
+ * {@literal null} cases, too)
+ * @param properties must not be {@literal null} or contain {@literal null}
+ * or empty strings
+ */
+ public Sort(Direction direction, String... properties) {
+
+ this(direction, properties == null ? new ArrayList() : Arrays
+ .asList(properties));
+ }
+
+
+ /**
+ * Creates a new {@link Sort} instance.
+ *
+ * @param direction
+ * @param properties
+ */
+ public Sort(Direction direction, List properties) {
+
+ if (properties == null || properties.isEmpty()) {
+ throw new IllegalArgumentException(
+ "You have to provide at least one property to sort by!");
+ }
+
+ this.orders = new ArrayList(properties.size());
+
+ for (String property : properties) {
+ this.orders.add(new Order(direction, property));
+ }
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Iterable#iterator()
+ */
+ public Iterator iterator() {
+
+ return this.orders.iterator();
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object obj) {
+
+ if (this == obj) {
+ return true;
+ }
+
+ if (!(obj instanceof Sort)) {
+ return false;
+ }
+
+ Sort that = (Sort) obj;
+
+ return this.orders.equals(that.orders);
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+
+ int result = 17;
+ result = 31 * result + orders.hashCode();
+ return result;
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+
+ return StringUtils.collectionToCommaDelimitedString(orders);
+ }
+
+ /**
+ * Enumeration for sort directions.
+ *
+ * @author Oliver Gierke
+ */
+ public static enum Direction {
+
+ ASC, DESC;
+
+ /**
+ * Returns the {@link Direction} enum for the given {@link String}
+ * value.
+ *
+ * @param value
+ * @return
+ */
+ public static Direction fromString(String value) {
+
+ try {
+ return Direction.valueOf(value.toUpperCase(Locale.US));
+ } catch (Exception e) {
+ throw new IllegalArgumentException(
+ String.format(
+ "Invalid value '%s' for orders given! Has to be either 'desc' or 'asc'.",
+ value), e);
+ }
+ }
+ }
+
+ /**
+ * Property implements the pairing of an {@code Order} and a property. It is
+ * used to provide input for {@link Sort}
+ *
+ * @author Oliver Gierke
+ */
+ public static class Order {
+
+ private final Direction direction;
+ private final String property;
+
+
+ /**
+ * Creates a new {@link Order} instance. if order is {@literal null}
+ * then order defaults to {@value Sort#DEFAULT_DIRECTION}
+ *
+ * @param direction can be {@code null}
+ * @param property must not be {@code null} or empty
+ */
+ public Order(Direction direction, String property) {
+
+ if (property == null || "".equals(property.trim())) {
+ throw new IllegalArgumentException(
+ "Property must not null or empty!");
+ }
+
+ this.direction = direction == null ? DEFAULT_DIRECTION : direction;
+ this.property = property;
+ }
+
+
+ /**
+ * Creates a new {@link Order} instance. Takes a single property. Order
+ * defaults to {@value Sort.DEFAULT_ORDER}
+ *
+ * @param property - must not be {@code null} or empty
+ */
+ public Order(String property) {
+
+ this(DEFAULT_DIRECTION, property);
+ }
+
+
+ public static List create(Direction direction,
+ Iterable properties) {
+
+ List orders = new ArrayList();
+ for (String property : properties) {
+ orders.add(new Order(direction, property));
+ }
+ return orders;
+ }
+
+
+ /**
+ * Returns the order the property shall be sorted for.
+ *
+ * @return
+ */
+ public Direction getDirection() {
+
+ return direction;
+ }
+
+
+ /**
+ * Returns the property to order for.
+ *
+ * @return
+ */
+ public String getProperty() {
+
+ return property;
+ }
+
+
+ /**
+ * Returns whether sorting for this property shall be ascending.
+ *
+ * @return
+ */
+ public boolean isAscending() {
+
+ return this.direction.equals(Direction.ASC);
+ }
+
+
+ /**
+ * Returns a new {@link Order} with the given {@link Order}.
+ *
+ * @param order
+ * @return
+ */
+ public Order with(Direction order) {
+
+ return new Order(order, this.property);
+ }
+
+
+ /**
+ * Returns a new {@link Sort} instance for the given properties.
+ *
+ * @param properties
+ * @return
+ */
+ public Sort withProperties(String... properties) {
+
+ return new Sort(this.direction, properties);
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+
+ int result = 17;
+
+ result = 31 * result + direction.hashCode();
+ result = 31 * result + property.hashCode();
+
+ return result;
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object obj) {
+
+ if (this == obj) {
+ return true;
+ }
+
+ if (!(obj instanceof Order)) {
+ return false;
+ }
+
+ Order that = (Order) obj;
+
+ return this.direction.equals(that.direction)
+ && this.property.equals(that.property);
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+
+ return String.format("%s: %s", property, direction);
+ }
+ }
+}
diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/NoRepositoryBean.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/NoRepositoryBean.java
new file mode 100644
index 000000000..376bf3e1d
--- /dev/null
+++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/NoRepositoryBean.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2008-2010 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;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+
+/**
+ * Annotation to exclude repository interfaces from being picked up and thus in
+ * consequence getting an instance being created.
+ *
+ * This will typically be used when providing an extended base interface for all
+ * repositories in combination with a custom repository base class to implement
+ * methods declared in that intermediate interface. In this case you typically
+ * derive your concrete repository interfaces from the intermediate one but
+ * don't want to create a Spring bean for the intermediate interface.
+ *
+ * @author Oliver Gierke
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@Documented
+public @interface NoRepositoryBean {
+
+}
diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/PagingAndSortingRepository.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/PagingAndSortingRepository.java
new file mode 100644
index 000000000..438781798
--- /dev/null
+++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/PagingAndSortingRepository.java
@@ -0,0 +1,40 @@
+package org.springframework.data.repository;
+
+import java.io.Serializable;
+import java.util.List;
+
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+
+
+/**
+ * Extension of {@link Repository} to provide additional methods to retrieve
+ * entities using the pagination and sorting abstraction.
+ *
+ * @see Sort
+ * @see Pageable
+ * @see Page
+ * @author Oliver Gierke
+ */
+public interface PagingAndSortingRepository extends
+ Repository {
+
+ /**
+ * Returns all entities sorted by the given options.
+ *
+ * @param sort
+ * @return all entities sorted by the given options
+ */
+ List findAll(Sort sort);
+
+
+ /**
+ * Returns a {@link Page} of entities meeting the paging restriction
+ * provided in the {@code Pageable} object.
+ *
+ * @param pageable
+ * @return a page of entities
+ */
+ Page findAll(Pageable pageable);
+}
diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/Repository.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/Repository.java
new file mode 100644
index 000000000..a20ec8415
--- /dev/null
+++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/Repository.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2008-2010 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;
+
+import java.io.Serializable;
+import java.util.List;
+
+
+/**
+ * Interface for generic CRUD operations on a repository for a specific type.
+ *
+ * @author Oliver Gierke
+ * @author Eberhard Wolff
+ */
+public interface Repository {
+
+ /**
+ * Saves a given entity. Use the returned instance for further operations as
+ * the save operation might have changed the entity instance completely.
+ *
+ * @param entity
+ * @return the saved entity
+ */
+ T save(T entity);
+
+
+ /**
+ * Saves all given entities.
+ *
+ * @param entities
+ * @return
+ */
+ List save(Iterable extends T> entities);
+
+
+ /**
+ * Retrives an entity by its primary key.
+ *
+ * @param id
+ * @return the entity with the given primary key or {@code null} if none
+ * found
+ * @throws IllegalArgumentException if primaryKey is {@code null}
+ */
+ T findById(ID id);
+
+
+ /**
+ * Returns whether an entity with the given id exists.
+ *
+ * @param id
+ * @return true if an entity with the given id exists, alse otherwise
+ * @throws IllegalArgumentException if primaryKey is {@code null}
+ */
+ boolean exists(ID id);
+
+
+ /**
+ * Returns all instances of the type.
+ *
+ * @return all entities
+ */
+ List findAll();
+
+
+ /**
+ * Returns the number of entities available.
+ *
+ * @return the number of entities
+ */
+ Long count();
+
+
+ /**
+ * Deletes a given entity.
+ *
+ * @param entity
+ */
+ void delete(T entity);
+
+
+ /**
+ * Deletes the given entities.
+ *
+ * @param entities
+ */
+ void delete(Iterable extends T> entities);
+
+
+ /**
+ * Deletes all entities managed by the DAO.
+ */
+ void deleteAll();
+}
diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/AbstractRepositoryConfigDefinitionParser.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/AbstractRepositoryConfigDefinitionParser.java
new file mode 100644
index 000000000..dd876f9b0
--- /dev/null
+++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/AbstractRepositoryConfigDefinitionParser.java
@@ -0,0 +1,478 @@
+/*
+ * Copyright 2008-2010 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.springframework.beans.factory.support.BeanDefinitionReaderUtils.*;
+import static org.springframework.data.repository.util.ClassUtils.*;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.parsing.BeanComponentDefinition;
+import org.springframework.beans.factory.parsing.ReaderContext;
+import org.springframework.beans.factory.support.AbstractBeanDefinition;
+import org.springframework.beans.factory.support.BeanDefinitionBuilder;
+import org.springframework.beans.factory.support.BeanDefinitionRegistry;
+import org.springframework.beans.factory.xml.BeanDefinitionParser;
+import org.springframework.beans.factory.xml.ParserContext;
+import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.core.type.classreading.MetadataReader;
+import org.springframework.core.type.classreading.MetadataReaderFactory;
+import org.springframework.core.type.filter.AnnotationTypeFilter;
+import org.springframework.core.type.filter.AssignableTypeFilter;
+import org.springframework.core.type.filter.RegexPatternTypeFilter;
+import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
+import org.springframework.data.repository.NoRepositoryBean;
+import org.w3c.dom.Element;
+
+
+/**
+ * Base class to implement repository namespaces. These will typically consist
+ * of a main XML element potentially having child elements. The parser will wrap
+ * the XML element into a {@link GlobalRepositoryConfigInformation} object and
+ * allow either manual configuration or automatic detection of repository
+ * interfaces.
+ *
+ * @author Oliver Gierke
+ */
+public abstract class AbstractRepositoryConfigDefinitionParser, T extends SingleRepositoryConfigInformation>
+ implements BeanDefinitionParser {
+
+ private static final Logger LOG = LoggerFactory
+ .getLogger(AbstractRepositoryConfigDefinitionParser.class);
+
+ private static final Class> PET_POST_PROCESSOR =
+ PersistenceExceptionTranslationPostProcessor.class;
+ private static final String DAO_INTERFACE_POST_PROCESSOR =
+ "org.springframework.data.repository.support.RepositoryInterfaceAwareBeanPostProcessor";
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.springframework.beans.factory.xml.BeanDefinitionParser#parse(org.
+ * w3c.dom.Element, org.springframework.beans.factory.xml.ParserContext)
+ */
+ public BeanDefinition parse(Element element, ParserContext parser) {
+
+ try {
+ S configContext = getGlobalRepositoryConfigInformation(element);
+
+ if (configContext.configureManually()) {
+ doManualConfiguration(configContext, parser);
+ } else {
+ doAutoConfiguration(configContext, parser);
+ }
+
+ Object beanSource = parser.extractSource(element);
+ registerPostProcessors(parser.getRegistry(), beanSource);
+
+ } catch (RuntimeException e) {
+ handleError(e, element, parser.getReaderContext());
+ }
+
+ return null;
+ }
+
+
+ /**
+ * Executes repository auto configuration by scanning the provided base
+ * package for repository interfaces.
+ *
+ * @param config
+ * @param parser
+ */
+ private void doAutoConfiguration(S config, ParserContext parser) {
+
+ LOG.debug("Triggering auto repository detection");
+
+ ResourceLoader resourceLoader =
+ parser.getReaderContext().getResourceLoader();
+
+ // Detect available DAO interfaces
+ Set repositoryInterfaces =
+ getRepositoryInterfacesForAutoConfig(config, resourceLoader,
+ parser.getReaderContext());
+
+ for (String daoInterface : repositoryInterfaces) {
+ registerGenericRepositoryFactoryBean(parser,
+ config.getAutoconfigRepositoryInformation(daoInterface));
+ }
+ }
+
+
+ private Set getRepositoryInterfacesForAutoConfig(S config,
+ ResourceLoader loader, ReaderContext reader) {
+
+ ClassPathScanningCandidateComponentProvider scanner =
+ new RepositoryComponentProvider(
+ config.getRepositoryBaseInterface());
+ scanner.setResourceLoader(loader);
+
+ TypeFilterParser parser =
+ new TypeFilterParser(loader.getClassLoader(), reader);
+ parser.parseFilters(config.getSource(), scanner);
+
+ Set findCandidateComponents =
+ scanner.findCandidateComponents(config.getBasePackage());
+
+ Set interfaceNames = new HashSet();
+ for (BeanDefinition definition : findCandidateComponents) {
+ interfaceNames.add(definition.getBeanClassName());
+ }
+
+ return interfaceNames;
+ }
+
+
+ /**
+ * Returns a {@link GlobalRepositoryConfigInformation} implementation for
+ * the given element.
+ *
+ * @param element
+ * @return
+ */
+ protected abstract S getGlobalRepositoryConfigInformation(Element element);
+
+
+ /**
+ * Proceeds manual configuration by traversing the context's
+ * {@link SingleRepositoryConfigInformation}s.
+ *
+ * @param context
+ * @param parser
+ */
+ private void doManualConfiguration(S context, ParserContext parser) {
+
+ LOG.debug("Triggering manual repository detection");
+
+ for (T daoContext : context.getSingleRepositoryConfigInformations()) {
+ registerGenericRepositoryFactoryBean(parser, daoContext);
+ }
+ }
+
+
+ private void handleError(Exception e, Element source, ReaderContext reader) {
+
+ reader.error(e.getMessage(), reader.extractSource(source), e.getCause());
+ }
+
+
+ /**
+ * Registers a generic repository factory bean for a bean with the given
+ * name and the provided configuration context.
+ *
+ * @param parser
+ * @param name
+ * @param context
+ */
+ private void registerGenericRepositoryFactoryBean(ParserContext parser,
+ T context) {
+
+ try {
+
+ Object beanSource = parser.extractSource(context.getSource());
+
+ BeanDefinitionBuilder builder =
+ BeanDefinitionBuilder.rootBeanDefinition(context
+ .getRepositoryFactoryBeanClassName());
+
+ builder.addPropertyValue("repositoryInterface",
+ context.getInterfaceName());
+ builder.addPropertyValue("queryLookupStrategyKey",
+ context.getQueryLookupStrategyKey());
+
+ String customImplementationBeanName =
+ registerCustomImplementation(context, parser, beanSource);
+
+ if (customImplementationBeanName != null) {
+ builder.addPropertyReference("customImplementation",
+ customImplementationBeanName);
+ }
+
+ postProcessBeanDefinition(context, builder, beanSource);
+
+ AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
+ beanDefinition.setSource(beanSource);
+
+ LOG.debug(
+ "Registering repository: %s - Interface: %s - Factory: %s, - Custom implementation: %s",
+ new Object[] { context.getBeanId(),
+ context.getInterfaceName(),
+ context.getRepositoryFactoryBeanClassName(),
+ customImplementationBeanName });
+
+ BeanComponentDefinition definition =
+ new BeanComponentDefinition(beanDefinition,
+ context.getBeanId());
+ parser.registerBeanComponent(definition);
+ } catch (RuntimeException e) {
+ handleError(e, context.getSource(), parser.getReaderContext());
+ }
+ }
+
+
+ /**
+ * Callback to post process a repository bean definition prior to actual
+ * registration.
+ *
+ * @param context
+ * @param builder
+ * @param beanSource
+ */
+ protected void postProcessBeanDefinition(T context,
+ BeanDefinitionBuilder builder, Object beanSource) {
+
+ }
+
+
+ /**
+ * Registers a possibly available custom repository implementation on the
+ * repository bean. Tries to find an already registered bean to reference or
+ * tries to detect a custom implementation itself.
+ *
+ * @param config
+ * @param parser
+ * @param source
+ * @return the bean name of the custom implementation or {@code null} if
+ * none available
+ */
+ private String registerCustomImplementation(T config, ParserContext parser,
+ Object source) {
+
+ String beanName = config.getImplementationBeanName();
+
+ // Already a bean configured?
+ if (parser.getRegistry().containsBeanDefinition(beanName)) {
+ return beanName;
+ }
+
+ // Autodetect implementation
+ if (config.autodetectCustomImplementation()) {
+
+ AbstractBeanDefinition beanDefinition =
+ detectCustomImplementation(config, parser);
+
+ if (null == beanDefinition) {
+ return null;
+ }
+
+ LOG.debug("Registering custom repository implementation: %s %s",
+ config.getImplementationBeanName(),
+ beanDefinition.getBeanClassName());
+
+ beanDefinition.setSource(source);
+ parser.registerBeanComponent(new BeanComponentDefinition(
+ beanDefinition, beanName));
+
+ } else {
+ beanName = config.getCustomImplementationRef();
+ }
+
+ return beanName;
+ }
+
+
+ /**
+ * Tries to detect a custom implementation for a repository bean by
+ * classpath scanning.
+ *
+ * @param config
+ * @param parser
+ * @return the {@code AbstractBeanDefinition} of the custom implementation
+ * or {@literal null} if none found
+ */
+ private AbstractBeanDefinition detectCustomImplementation(T config,
+ ParserContext parser) {
+
+ // Build pattern to lookup implementation class
+ Pattern pattern =
+ Pattern.compile(".*" + config.getImplementationClassName());
+
+ // Build classpath scanner and lookup bean definition
+ ClassPathScanningCandidateComponentProvider provider =
+ new ClassPathScanningCandidateComponentProvider(false);
+ provider.setResourceLoader(parser.getReaderContext()
+ .getResourceLoader());
+ provider.addIncludeFilter(new RegexPatternTypeFilter(pattern));
+ Set definitions =
+ provider.findCandidateComponents(config.getBasePackage());
+
+ return (0 == definitions.size() ? null
+ : (AbstractBeanDefinition) definitions.iterator().next());
+ }
+
+
+ /**
+ * Registers necessary (Bean)PostProcessor instances if they have not
+ * already been registered.
+ *
+ * @param registry
+ * @param source
+ */
+ protected void registerPostProcessors(BeanDefinitionRegistry registry,
+ Object source) {
+
+ // Create PersistenceExceptionTranslationPostProcessor definition
+ if (!hasBean(PET_POST_PROCESSOR, registry)) {
+
+ AbstractBeanDefinition definition =
+ BeanDefinitionBuilder
+ .rootBeanDefinition(PET_POST_PROCESSOR)
+ .getBeanDefinition();
+
+ registerWithSourceAndGeneratedBeanName(registry, definition, source);
+ }
+
+ AbstractBeanDefinition definition =
+ BeanDefinitionBuilder.rootBeanDefinition(
+ DAO_INTERFACE_POST_PROCESSOR).getBeanDefinition();
+
+ registerWithSourceAndGeneratedBeanName(registry, definition, source);
+ }
+
+
+ /**
+ * Returns whether the given {@link BeanDefinitionRegistry} already contains
+ * a bean of the given type assuming the bean name has been autogenerated.
+ *
+ * @param type
+ * @param registry
+ * @return
+ */
+ protected static boolean hasBean(Class> type,
+ BeanDefinitionRegistry registry) {
+
+ String name =
+ String.format("%s%s0", type.getName(),
+ GENERATED_BEAN_NAME_SEPARATOR);
+ return registry.containsBeanDefinition(name);
+ }
+
+
+ /**
+ * Sets the given source on the given {@link AbstractBeanDefinition} and
+ * registers it inside the given {@link BeanDefinitionRegistry}.
+ *
+ * @param registry
+ * @param bean
+ * @param source
+ * @return
+ */
+ protected static String registerWithSourceAndGeneratedBeanName(
+ BeanDefinitionRegistry registry, AbstractBeanDefinition bean,
+ Object source) {
+
+ bean.setSource(source);
+
+ String beanName = generateBeanName(bean, registry);
+ registry.registerBeanDefinition(beanName, bean);
+
+ return beanName;
+ }
+
+ /**
+ * Custom {@link ClassPathScanningCandidateComponentProvider} scanning for
+ * interfaces extending the given base interface. Skips interfaces annotated
+ * with {@link NoRepositoryBean}.
+ *
+ * @author Oliver Gierke
+ */
+ static class RepositoryComponentProvider extends
+ ClassPathScanningCandidateComponentProvider {
+
+ /**
+ * Creates a new {@link RepositoryComponentProvider}.
+ *
+ * @param repositoryInterface the interface to scan for
+ */
+ public RepositoryComponentProvider(Class> repositoryInterface) {
+
+ super(false);
+ addIncludeFilter(new InterfaceTypeFilter(repositoryInterface));
+ addExcludeFilter(new AnnotationTypeFilter(NoRepositoryBean.class));
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @seeorg.springframework.context.annotation.
+ * ClassPathScanningCandidateComponentProvider
+ * #isCandidateComponent(org.springframework
+ * .beans.factory.annotation.AnnotatedBeanDefinition)
+ */
+ @Override
+ protected boolean isCandidateComponent(
+ AnnotatedBeanDefinition beanDefinition) {
+
+ boolean isNonHadesInterfaces =
+ !isGenericRepositoryInterface(beanDefinition
+ .getBeanClassName());
+ boolean isTopLevelType =
+ !beanDefinition.getMetadata().hasEnclosingClass();
+
+ return isNonHadesInterfaces && isTopLevelType;
+ }
+
+ /**
+ * {@link org.springframework.core.type.filter.TypeFilter} that only
+ * matches interfaces. Thus setting this up makes only sense providing
+ * an interface type as {@code targetType}.
+ *
+ * @author Oliver Gierke
+ */
+ private static class InterfaceTypeFilter extends AssignableTypeFilter {
+
+ /**
+ * Creates a new {@link InterfaceTypeFilter}.
+ *
+ * @param targetType
+ */
+ public InterfaceTypeFilter(Class> targetType) {
+
+ super(targetType);
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @seeorg.springframework.core.type.filter.
+ * AbstractTypeHierarchyTraversingFilter
+ * #match(org.springframework.core.type.classreading.MetadataReader,
+ * org.springframework.core.type.classreading.MetadataReaderFactory)
+ */
+ @Override
+ public boolean match(MetadataReader metadataReader,
+ MetadataReaderFactory metadataReaderFactory)
+ throws IOException {
+
+ return metadataReader.getClassMetadata().isInterface()
+ && super.match(metadataReader, metadataReaderFactory);
+ }
+ }
+ }
+}
diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/AutomaticRepositoryConfigInformation.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/AutomaticRepositoryConfigInformation.java
new file mode 100644
index 000000000..91df44cd6
--- /dev/null
+++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/AutomaticRepositoryConfigInformation.java
@@ -0,0 +1,61 @@
+package org.springframework.data.repository.config;
+
+import static org.springframework.util.ClassUtils.*;
+import static org.springframework.util.StringUtils.*;
+
+import org.springframework.util.Assert;
+
+
+/**
+ * A {@link SingleRepositoryConfigInformation} implementation that is not backed
+ * by an XML element but by a scanned interface. As this is derived from the
+ * parent, most of the lookup logic is delegated to the parent as well.
+ *
+ * @author Oliver Gierke
+ */
+public class AutomaticRepositoryConfigInformation
+ extends ParentDelegatingRepositoryConfigInformation {
+
+ private final String interfaceName;
+
+
+ /**
+ * Creates a new {@link AutomaticRepositoryConfigInformation} for the given
+ * interface name and {@link CommonRepositoryConfigInformation} parent.
+ *
+ * @param interfaceName
+ * @param parent
+ */
+ public AutomaticRepositoryConfigInformation(String interfaceName, S parent) {
+
+ super(parent);
+ Assert.notNull(interfaceName);
+ this.interfaceName = interfaceName;
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.springframework.data.repository.config.SingleRepositoryConfigInformation
+ * #getBeanId()
+ */
+ public String getBeanId() {
+
+ return uncapitalize(getShortName(interfaceName));
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.springframework.data.repository.config.SingleRepositoryConfigInformation
+ * #getInterfaceName()
+ */
+ public String getInterfaceName() {
+
+ return interfaceName;
+ }
+}
diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/CommonRepositoryConfigInformation.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/CommonRepositoryConfigInformation.java
new file mode 100644
index 000000000..9d827d536
--- /dev/null
+++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/CommonRepositoryConfigInformation.java
@@ -0,0 +1,64 @@
+package org.springframework.data.repository.config;
+
+import org.springframework.data.repository.query.QueryLookupStrategy.Key;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.w3c.dom.Element;
+
+
+/**
+ * Interface for shared repository information.
+ *
+ * @author Oliver Gierke
+ */
+public interface CommonRepositoryConfigInformation {
+
+ /**
+ * Returns the element the repository information is derived from.
+ *
+ * @return
+ */
+ Element getSource();
+
+
+ /**
+ * Returns the base package.
+ *
+ * @return
+ */
+ String getBasePackage();
+
+
+ /**
+ * Returns the suffix to use for implementation bean lookup or class
+ * detection.
+ *
+ * @return
+ */
+ String getRepositoryImplementationSuffix();
+
+
+ /**
+ * Returns the configured repository factory class.
+ *
+ * @return
+ */
+ String getRepositoryFactoryBeanClassName();
+
+
+ /**
+ * Returns the bean name of the {@link PlatformTransactionManager} to be
+ * used.
+ *
+ * @return
+ */
+ String getTransactionManagerRef();
+
+
+ /**
+ * Returns the strategy finder methods should be resolved.
+ *
+ * @return
+ */
+ Key getQueryLookupStrategyKey();
+
+}
\ No newline at end of file
diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/GlobalRepositoryConfigInformation.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/GlobalRepositoryConfigInformation.java
new file mode 100644
index 000000000..c4b990ad2
--- /dev/null
+++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/GlobalRepositoryConfigInformation.java
@@ -0,0 +1,43 @@
+package org.springframework.data.repository.config;
+
+/**
+ * @author Oliver Gierke
+ */
+public interface GlobalRepositoryConfigInformation>
+ extends CommonRepositoryConfigInformation {
+
+ /**
+ * Returns the
+ *
+ * @param interfaceName
+ * @return
+ */
+ T getAutoconfigRepositoryInformation(String interfaceName);
+
+
+ /**
+ * Returns all {@link SingleRepositoryConfigInformation} instances used for
+ * manual configuration.
+ *
+ * @return
+ */
+ Iterable getSingleRepositoryConfigInformations();
+
+
+ /**
+ * Returns whether to consider manual configuration. If this returns true,
+ * clients should use {@link #getSingleRepositoryConfigInformations()} to
+ * lookup configuration information for individual repository beans.
+ *
+ * @return
+ */
+ boolean configureManually();
+
+
+ /**
+ * Returns the base interface to use
+ *
+ * @return
+ */
+ Class> getRepositoryBaseInterface();
+}
\ No newline at end of file
diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/ManualRepositoryConfigInformation.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/ManualRepositoryConfigInformation.java
new file mode 100644
index 000000000..939c45e74
--- /dev/null
+++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/ManualRepositoryConfigInformation.java
@@ -0,0 +1,165 @@
+package org.springframework.data.repository.config;
+
+import static org.springframework.util.StringUtils.*;
+
+import org.springframework.data.repository.query.QueryLookupStrategy.Key;
+import org.w3c.dom.Element;
+
+
+/**
+ * @author Oliver Gierke
+ */
+public class ManualRepositoryConfigInformation
+ extends ParentDelegatingRepositoryConfigInformation {
+
+ private static final String CUSTOM_IMPL_REF = "custom-impl-ref";
+
+ private Element element;
+
+
+ /**
+ * @param parent
+ */
+ public ManualRepositoryConfigInformation(Element element, T parent) {
+
+ super(parent);
+ this.element = element;
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.springframework.data.jpa.repository.config.RepositoryInformation#
+ * getBeanName()
+ */
+ public String getBeanId() {
+
+ return element.getAttribute("id");
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.springframework.data.jpa.repository.config.RepositoryInformation#
+ * getInterfaceName()
+ */
+ public String getInterfaceName() {
+
+ return getBasePackage() + "." + capitalize(getBeanId());
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.springframework.data.jpa.repository.config.RepositoryInformation#
+ * getCustomImplementationRef()
+ */
+ @Override
+ public String getCustomImplementationRef() {
+
+ return element.getAttribute(CUSTOM_IMPL_REF);
+ }
+
+
+ /**
+ * Returns if a custom DAO implementation shall be autodetected.
+ *
+ * @return
+ */
+ @Override
+ public boolean autodetectCustomImplementation() {
+
+ return !hasText(getCustomImplementationRef());
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.springframework.data.jpa.repository.config.AbstractRepositoryInformation
+ * #getRepositoryImplementationSuffix()
+ */
+ @Override
+ public String getRepositoryImplementationSuffix() {
+
+ String value =
+ element.getAttribute(RepositoryConfig.REPOSITORY_IMPL_POSTFIX);
+ return hasText(value) ? value : getParent()
+ .getRepositoryImplementationSuffix();
+ }
+
+
+ @Override
+ public String getTransactionManagerRef() {
+
+ return getAttribute(RepositoryConfig.TRANSACTION_MANAGER_REF);
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.springframework.data.jpa.repository.config.CommonRepositoryInformation
+ * #getSource()
+ */
+ @Override
+ public Element getSource() {
+
+ return element;
+ }
+
+
+ /**
+ * Returns the attribute of the current context. If it's not set the method
+ * will fall back to the parent's source.
+ *
+ * @param attribute
+ * @return
+ */
+ protected String getAttribute(String attribute) {
+
+ String value = getSource().getAttribute(attribute);
+ return hasText(value) ? value : getParent().getSource().getAttribute(
+ attribute);
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.springframework.data.jpa.repository.config.CommonRepositoryInformation
+ * #getRepositoryFactoryClassName()
+ */
+ @Override
+ public String getRepositoryFactoryBeanClassName() {
+
+ String value =
+ element.getAttribute(RepositoryConfig.REPOSITORY_FACTORY_CLASS_NAME);
+ return hasText(value) ? value : getParent()
+ .getRepositoryFactoryBeanClassName();
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.springframework.data.jpa.repository.config.CommonRepositoryInformation
+ * #getQueryLookupStrategyKey()
+ */
+ @Override
+ public Key getQueryLookupStrategyKey() {
+
+ return Key
+ .create(getAttribute(RepositoryConfig.QUERY_LOOKUP_STRATEGY));
+ }
+}
diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/ParentDelegatingRepositoryConfigInformation.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/ParentDelegatingRepositoryConfigInformation.java
new file mode 100644
index 000000000..571f05242
--- /dev/null
+++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/ParentDelegatingRepositoryConfigInformation.java
@@ -0,0 +1,177 @@
+package org.springframework.data.repository.config;
+
+import static org.springframework.util.StringUtils.*;
+
+import org.springframework.data.repository.query.QueryLookupStrategy.Key;
+import org.springframework.util.Assert;
+import org.w3c.dom.Element;
+
+
+/**
+ * Base class for {@link SingleRepositoryConfigInformation} implementations. So these
+ * implementations will capture information for XML elements manually
+ * configuring a single repository bean.
+ *
+ * @author Oliver Gierke
+ */
+public abstract class ParentDelegatingRepositoryConfigInformation
+ implements SingleRepositoryConfigInformation {
+
+ private final T parent;
+
+
+ /**
+ * Creates a new {@link ParentDelegatingRepositoryConfigInformation} with the given
+ * {@link CommonRepositoryConfigInformation} as parent.
+ *
+ * @param parent
+ */
+ public ParentDelegatingRepositoryConfigInformation(T parent) {
+
+ Assert.notNull(parent);
+ this.parent = parent;
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.springframework.data.jpa.repository.config.RepositoryInformation#
+ * getParent()
+ */
+ protected T getParent() {
+
+ return parent;
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.springframework.data.jpa.repository.config.CommonRepositoryInformation
+ * #getBasePackage()
+ */
+ public String getBasePackage() {
+
+ return parent.getBasePackage();
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.springframework.data.jpa.repository.config.RepositoryInformation#
+ * getImplementationClassName()
+ */
+ public String getImplementationClassName() {
+
+ return capitalize(getBeanId()) + getRepositoryImplementationSuffix();
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.springframework.data.jpa.repository.config.RepositoryInformation#
+ * getImplementationBeanName()
+ */
+ public String getImplementationBeanName() {
+
+ return getBeanId() + getRepositoryImplementationSuffix();
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.springframework.data.jpa.repository.config.RepositoryInformation#
+ * autodetectCustomImplementation()
+ */
+ public boolean autodetectCustomImplementation() {
+
+ return true;
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.springframework.data.jpa.repository.config.RepositoryInformation#
+ * getCustomImplementationRef()
+ */
+ public String getCustomImplementationRef() {
+
+ return getBeanId() + getRepositoryImplementationSuffix();
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.springframework.data.jpa.repository.config.CommonRepositoryInformation
+ * #getSource()
+ */
+ public Element getSource() {
+
+ return parent.getSource();
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.springframework.data.jpa.repository.config.RepositoryInformation#
+ * getDaoImplPostfix()
+ */
+ public String getRepositoryImplementationSuffix() {
+
+ return parent.getRepositoryImplementationSuffix();
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.springframework.data.jpa.repository.config.RepositoryInformation#
+ * getDaoFactoryClassName()
+ */
+ public String getRepositoryFactoryBeanClassName() {
+
+ return parent.getRepositoryFactoryBeanClassName();
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.springframework.data.jpa.repository.config.RepositoryInformation#
+ * getTransactionManagerRef()
+ */
+ public String getTransactionManagerRef() {
+
+ return parent.getTransactionManagerRef();
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.springframework.data.jpa.repository.config.RepositoryInformation#
+ * getQueryLookupStrategyKey()
+ */
+ public Key getQueryLookupStrategyKey() {
+
+ return parent.getQueryLookupStrategyKey();
+ }
+}
diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/RepositoryConfig.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/RepositoryConfig.java
new file mode 100644
index 000000000..3b3b93611
--- /dev/null
+++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/RepositoryConfig.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright 2008-2010 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 java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.springframework.data.repository.query.QueryLookupStrategy.Key;
+import org.springframework.data.repository.util.TxUtils;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+
+/**
+ * Class defining access to the repository configuration abstracting the content
+ * of the {@code repositories} element in XML namespcae configuration. Defines
+ * default values to populate resulting repository beans with.
+ *
+ * @author Oliver Gierke
+ */
+public abstract class RepositoryConfig, S extends CommonRepositoryConfigInformation>
+ implements GlobalRepositoryConfigInformation {
+
+ public static final String DEFAULT_DAO_IMPL_POSTFIX = "Impl";
+ public static final String QUERY_LOOKUP_STRATEGY = "query-lookup-strategy";
+ public static final String BASE_PACKAGE = "base-package";
+ public static final String REPOSITORY_IMPL_POSTFIX = "dao-impl-postfix";
+ public static final String REPOSITORY_FACTORY_CLASS_NAME = "factory-class";
+ public static final String TRANSACTION_MANAGER_REF =
+ "transaction-manager-ref";
+
+ private final Element element;
+ private final String defaultRepositoryFactoryBeanClassName;
+
+
+ /**
+ * Creates an instance of {@code RepositoryConfig}.
+ *
+ * @param repositoriesElement
+ */
+ protected RepositoryConfig(Element repositoriesElement,
+ String defaultRepositoryFactoryBeanClassName) {
+
+ Assert.notNull(repositoriesElement, "Element must not be null!");
+ Assert.notNull(defaultRepositoryFactoryBeanClassName,
+ "Default repository factory bean class name must not be null!");
+
+ this.element = repositoriesElement;
+ this.defaultRepositoryFactoryBeanClassName =
+ defaultRepositoryFactoryBeanClassName;
+
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.springframework.data.repository.config.CommonRepositoryConfigInformation
+ * #getSource()
+ */
+ public Element getSource() {
+
+ return element;
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.springframework.data.repository.config.GlobalRepositoryConfigInformation
+ * #configureManually()
+ */
+ public boolean configureManually() {
+
+ return getRepositoryNodes().size() > 0;
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.springframework.data.repository.config.CommonRepositoryConfigInformation
+ * #getQueryLookupStrategyKey()
+ */
+ public Key getQueryLookupStrategyKey() {
+
+ String createFinderQueries =
+ element.getAttribute(QUERY_LOOKUP_STRATEGY);
+
+ return StringUtils.hasText(createFinderQueries) ? Key
+ .create(createFinderQueries) : null;
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.springframework.data.repository.config.CommonRepositoryConfigInformation
+ * #getBasePackage()
+ */
+ public String getBasePackage() {
+
+ return element.getAttribute(BASE_PACKAGE);
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.springframework.data.repository.config.CommonRepositoryConfigInformation
+ * #getRepositoryFactoryClassName()
+ */
+ public String getRepositoryFactoryBeanClassName() {
+
+ String factoryClassName =
+ getSource().getAttribute(REPOSITORY_FACTORY_CLASS_NAME);
+ return StringUtils.hasText(factoryClassName) ? factoryClassName
+ : defaultRepositoryFactoryBeanClassName;
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.springframework.data.repository.config.CommonRepositoryConfigInformation
+ * #getRepositoryImplementationSuffix()
+ */
+ public String getRepositoryImplementationSuffix() {
+
+ String postfix = element.getAttribute(REPOSITORY_IMPL_POSTFIX);
+ return StringUtils.hasText(postfix) ? postfix
+ : DEFAULT_DAO_IMPL_POSTFIX;
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.springframework.data.repository.config.CommonRepositoryConfigInformation
+ * #getTransactionManagerRef()
+ */
+ public String getTransactionManagerRef() {
+
+ String ref = element.getAttribute(TRANSACTION_MANAGER_REF);
+ return StringUtils.hasText(ref) ? ref
+ : TxUtils.DEFAULT_TRANSACTION_MANAGER;
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.springframework.data.jpa.repository.config.GlobalRepositoryInformation
+ * #getManualRepositoryInformations()
+ */
+ public Iterable getSingleRepositoryConfigInformations() {
+
+ Set infos = new HashSet();
+ for (Element element : getRepositoryNodes()) {
+ infos.add(createSingleRepositoryConfigInformationFor(element));
+ }
+
+ return infos;
+ }
+
+
+ private Collection getRepositoryNodes() {
+
+ NodeList nodes = element.getChildNodes();
+ Set result = new HashSet();
+
+ for (int i = 0; i < nodes.getLength(); i++) {
+
+ Node node = nodes.item(i);
+
+ boolean isElement = Node.ELEMENT_NODE == node.getNodeType();
+ boolean isDao = "repository".equals(node.getLocalName());
+
+ if (isElement && isDao) {
+ result.add((Element) node);
+ }
+ }
+
+ return result;
+ }
+
+
+ /**
+ * Creates a {@link SingleRepositoryConfigInformation} for the given
+ * {@link Element}.
+ *
+ * @param element
+ * @return
+ */
+ protected abstract T createSingleRepositoryConfigInformationFor(
+ Element element);
+}
diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/SingleRepositoryConfigInformation.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/SingleRepositoryConfigInformation.java
new file mode 100644
index 000000000..4d59bc8bd
--- /dev/null
+++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/SingleRepositoryConfigInformation.java
@@ -0,0 +1,60 @@
+package org.springframework.data.repository.config;
+
+/**
+ * Interface to capture configuration information necessary to set up a single
+ * repository instance.
+ *
+ * @author Oliver Gierke
+ */
+public interface SingleRepositoryConfigInformation
+ extends CommonRepositoryConfigInformation {
+
+ /**
+ * Returns the bean name to be used for the repository.
+ *
+ * @return
+ */
+ String getBeanId();
+
+
+ /**
+ * Returns the name of the repository interface.
+ *
+ * @return
+ */
+ String getInterfaceName();
+
+
+ /**
+ * Returns the class name of a possible custom repository implementation
+ * class to detect.
+ *
+ * @return
+ */
+ String getImplementationClassName();
+
+
+ /**
+ * Returns the bean name a possibly found custom implementation shall be
+ * registered under.
+ *
+ * @return
+ */
+ String getImplementationBeanName();
+
+
+ /**
+ * Returns the bean reference to the custom repository implementation.
+ *
+ * @return
+ */
+ String getCustomImplementationRef();
+
+
+ /**
+ * Returns whether to try to autodetect a custom implementation.
+ *
+ * @return
+ */
+ boolean autodetectCustomImplementation();
+}
\ No newline at end of file
diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/TypeFilterParser.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/TypeFilterParser.java
new file mode 100644
index 000000000..43171957a
--- /dev/null
+++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/TypeFilterParser.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright 2008-2010 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 java.lang.annotation.Annotation;
+import java.util.regex.Pattern;
+
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.FatalBeanException;
+import org.springframework.beans.factory.parsing.ReaderContext;
+import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
+import org.springframework.core.type.filter.AnnotationTypeFilter;
+import org.springframework.core.type.filter.AspectJTypeFilter;
+import org.springframework.core.type.filter.AssignableTypeFilter;
+import org.springframework.core.type.filter.RegexPatternTypeFilter;
+import org.springframework.core.type.filter.TypeFilter;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+
+/**
+ * Parser to populate the given
+ * {@link ClassPathScanningCandidateComponentProvider} with {@link TypeFilter}s
+ * parsed from the given {@link Element}'s children.
+ *
+ * @author Oliver Gierke
+ */
+class TypeFilterParser {
+
+ private static final String FILTER_TYPE_ATTRIBUTE = "type";
+ private static final String FILTER_EXPRESSION_ATTRIBUTE = "expression";
+
+ private final ClassLoader classLoader;
+ private final ReaderContext readerContext;
+
+
+ /**
+ * Creates a new {@link TypeFilterParser} with the given {@link ClassLoader}
+ * and {@link ReaderContext}.
+ *
+ * @param classLoader
+ * @param readerContext
+ */
+ public TypeFilterParser(ClassLoader classLoader, ReaderContext readerContext) {
+
+ this.classLoader = classLoader;
+ this.readerContext = readerContext;
+ }
+
+
+ /**
+ * Parses include and exclude filters form the given {@link Element}'s child
+ * elements and populates the given
+ * {@link ClassPathScanningCandidateComponentProvider} with the according
+ * {@link TypeFilter}s.
+ *
+ * @param element
+ * @param scanner
+ */
+ public void parseFilters(Element element,
+ ClassPathScanningCandidateComponentProvider scanner) {
+
+ parseTypeFilters(element, scanner, Type.INCLUDE);
+ parseTypeFilters(element, scanner, Type.EXCLUDE);
+ }
+
+
+ private void parseTypeFilters(Element element,
+ ClassPathScanningCandidateComponentProvider scanner, Type type) {
+
+ NodeList nodeList = element.getChildNodes();
+ for (int i = 0; i < nodeList.getLength(); i++) {
+ Node node = nodeList.item(i);
+
+ Element childElement = type.getElement(node);
+
+ if (childElement != null) {
+
+ try {
+
+ type.addFilter(
+ createTypeFilter((Element) node, classLoader),
+ scanner);
+
+ } catch (RuntimeException e) {
+ readerContext.error(e.getMessage(),
+ readerContext.extractSource(element), e.getCause());
+ }
+ }
+ }
+ }
+
+
+ protected TypeFilter createTypeFilter(Element element,
+ ClassLoader classLoader) {
+
+ String filterType = element.getAttribute(FILTER_TYPE_ATTRIBUTE);
+ String expression = element.getAttribute(FILTER_EXPRESSION_ATTRIBUTE);
+
+ try {
+
+ FilterType filter = FilterType.fromString(filterType);
+ return filter.getFilter(expression, classLoader);
+
+ } catch (ClassNotFoundException ex) {
+ throw new FatalBeanException("Type filter class not found: "
+ + expression, ex);
+ }
+ }
+
+ /**
+ * Enum representing all the filter types available for {@code include} and
+ * {@code exclude} elements. This acts as factory for {@link TypeFilter}
+ * instances.
+ *
+ * @see #getFilter(String, ClassLoader)
+ * @author Oliver Gierke
+ */
+ private static enum FilterType {
+
+ ANNOTATION {
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public TypeFilter getFilter(String expression,
+ ClassLoader classLoader) throws ClassNotFoundException {
+
+ return new AnnotationTypeFilter(
+ (Class) classLoader.loadClass(expression));
+ }
+ },
+
+ ASSIGNABLE {
+
+ @Override
+ public TypeFilter getFilter(String expression,
+ ClassLoader classLoader) throws ClassNotFoundException {
+
+ return new AssignableTypeFilter(
+ classLoader.loadClass(expression));
+ }
+
+ },
+
+ ASPECTJ {
+
+ @Override
+ public TypeFilter getFilter(String expression,
+ ClassLoader classLoader) {
+
+ return new AspectJTypeFilter(expression, classLoader);
+ }
+
+ },
+
+ REGEX {
+
+ @Override
+ public TypeFilter getFilter(String expression,
+ ClassLoader classLoader) {
+
+ return new RegexPatternTypeFilter(Pattern.compile(expression));
+ }
+
+ },
+
+ CUSTOM {
+
+ @Override
+ public TypeFilter getFilter(String expression,
+ ClassLoader classLoader) throws ClassNotFoundException {
+
+ Class> filterClass = classLoader.loadClass(expression);
+ if (!TypeFilter.class.isAssignableFrom(filterClass)) {
+ throw new IllegalArgumentException(
+ "Class is not assignable to ["
+ + TypeFilter.class.getName() + "]: "
+ + expression);
+ }
+ return (TypeFilter) BeanUtils.instantiateClass(filterClass);
+ }
+ };
+
+ /**
+ * Returns the {@link TypeFilter} for the given expression and
+ * {@link ClassLoader}.
+ *
+ * @param expression
+ * @param classLoader
+ * @return
+ * @throws ClassNotFoundException
+ */
+ abstract TypeFilter getFilter(String expression, ClassLoader classLoader)
+ throws ClassNotFoundException;
+
+
+ /**
+ * Returns the {@link FilterType} for the given type as {@link String}.
+ *
+ * @param typeString
+ * @return
+ * @throws IllegalArgumentException if no {@link FilterType} could be
+ * found for the given argument.
+ */
+ static FilterType fromString(String typeString) {
+
+ for (FilterType filter : FilterType.values()) {
+ if (filter.name().equalsIgnoreCase(typeString)) {
+ return filter;
+ }
+ }
+
+ throw new IllegalArgumentException("Unsupported filter type: "
+ + typeString);
+ }
+ }
+
+ private static enum Type {
+
+ INCLUDE("include-filter") {
+
+ @Override
+ public void addFilter(TypeFilter filter,
+ ClassPathScanningCandidateComponentProvider scanner) {
+
+ scanner.addIncludeFilter(filter);
+ }
+
+ },
+ EXCLUDE("exclude-filter") {
+
+ @Override
+ public void addFilter(TypeFilter filter,
+ ClassPathScanningCandidateComponentProvider scanner) {
+
+ scanner.addExcludeFilter(filter);
+ }
+ };
+
+ private String elementName;
+
+
+ private Type(String elementName) {
+
+ this.elementName = elementName;
+ }
+
+
+ /**
+ * Returns the {@link Element} if the given {@link Node} is an
+ * {@link Element} and it's name equals the one of the type.
+ *
+ * @param node
+ * @return
+ */
+ Element getElement(Node node) {
+
+ if (node.getNodeType() == Node.ELEMENT_NODE) {
+ String localName = node.getLocalName();
+ if (elementName.equals(localName)) {
+ return (Element) node;
+ }
+ }
+
+ return null;
+ }
+
+
+ abstract void addFilter(TypeFilter filter,
+ ClassPathScanningCandidateComponentProvider scanner);
+ }
+}
diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/Param.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/Param.java
new file mode 100644
index 000000000..7a0f49d5b
--- /dev/null
+++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/Param.java
@@ -0,0 +1,22 @@
+package org.springframework.data.repository.query;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+
+/**
+ * Annotation to bind let method parameters be bound to a query via a named
+ * parameter.
+ *
+ * @author Oliver Gierke
+ */
+@Target(ElementType.PARAMETER)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface Param {
+
+ String value();
+}
diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/Parameter.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/Parameter.java
new file mode 100644
index 000000000..9bc1d189b
--- /dev/null
+++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/Parameter.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright 2008-2010 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.query;
+
+import static java.lang.String.*;
+
+import java.lang.annotation.Annotation;
+import java.util.Arrays;
+import java.util.List;
+
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.util.Assert;
+
+
+/**
+ * Class to abstract a single parameter of a query method. It is held in the
+ * context of a {@link Parameters} instance.
+ *
+ * @author Oliver Gierke
+ */
+public final class Parameter {
+
+ @SuppressWarnings("unchecked")
+ static final List> TYPES = Arrays.asList(Pageable.class,
+ Sort.class);
+
+ private static final String PARAM_ON_SPECIAL = format(
+ "You must not user @%s on a parameter typed %s or %s",
+ Param.class.getSimpleName(), Pageable.class.getSimpleName(),
+ Sort.class.getSimpleName());
+
+ private static final String NAMED_PARAMETER_TEMPLATE = ":%s";
+ private static final String POSITION_PARAMETER_TEMPLATE = "?%s";
+
+ private final Class> type;
+ private final Parameters parameters;
+ private final int index;
+ private final String name;
+
+
+ /**
+ * Creates a new {@link Parameter} for the given type, {@link Annotation}s,
+ * positioned at the given index inside the given {@link Parameters}.
+ *
+ * @param type
+ * @param parameters
+ * @param index
+ * @param name
+ */
+ Parameter(Class> type, Parameters parameters, int index, String name) {
+
+ Assert.notNull(type);
+ Assert.notNull(parameters);
+
+ this.parameters = parameters;
+ this.index = index;
+
+ this.type = type;
+ this.name = name;
+
+ if (isSpecialParameter() && isNamedParameter()) {
+ throw new IllegalArgumentException(PARAM_ON_SPECIAL);
+ }
+ }
+
+
+ /**
+ * Copy constructor to put a {@link Parameter} into another context.
+ *
+ * @param parameter
+ * @param parameters
+ * @param index
+ */
+ Parameter(Parameter parameter, Parameters parameters, int index) {
+
+ this(parameter.type, parameters, index, parameter.name);
+ }
+
+
+ /**
+ * Returns whether the {@link Parameter} is the first one.
+ *
+ * @return
+ */
+ boolean isFirst() {
+
+ return index == 0;
+ }
+
+
+ /**
+ * Returns the next {@link Parameter} from the surrounding
+ * {@link Parameters}.
+ *
+ * @throws ParameterOutOfBoundsException
+ * @return
+ */
+ public Parameter getNext() {
+
+ return parameters.getParameter(index + 1);
+ }
+
+
+ /**
+ * Returns the previous {@link Parameter}.
+ *
+ * @return
+ */
+ Parameter getPrevious() {
+
+ return parameters.getParameter(index - 1);
+ }
+
+
+ /**
+ * Returns whether the parameter is a special parameter.
+ *
+ * @see #TYPES
+ * @param index
+ * @return
+ */
+ public boolean isSpecialParameter() {
+
+ return TYPES.contains(type);
+ }
+
+
+ /**
+ * Returns whether the {@link Parameter} is to be bound to a query.
+ *
+ * @return
+ */
+ public boolean isBindable() {
+
+ return !isSpecialParameter();
+ }
+
+
+ /**
+ * Returns the placeholder to be used for the parameter. Can either be a
+ * named one or positional.
+ *
+ * @param index
+ * @return
+ */
+ public String getPlaceholder() {
+
+ if (isNamedParameter()) {
+ return format(NAMED_PARAMETER_TEMPLATE, getName());
+ } else {
+ return format(POSITION_PARAMETER_TEMPLATE, getParameterPosition());
+ }
+ }
+
+
+ /**
+ * Returns the position index the parameter is bound to in the context of
+ * its surrounding {@link Parameters}.
+ *
+ * @return
+ */
+ public int getParameterPosition() {
+
+ return parameters.getPlaceholderPosition(this);
+ }
+
+
+ /**
+ * Returns whether the parameter is annotated with {@link Param}.
+ *
+ * @param index
+ * @return
+ */
+ public boolean isNamedParameter() {
+
+ return !isSpecialParameter() && getName() != null;
+ }
+
+
+ /**
+ * Returns the name of the parameter (through {@link Param} annotation) or
+ * null if none can be found.
+ *
+ * @return
+ */
+ public String getName() {
+
+ return name;
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+
+ return format("%s:%s", isNamedParameter() ? getName() : "#" + index,
+ type.getName());
+ }
+
+
+ /**
+ * Returns whether the {@link Parameter} is a {@link Pageable} parameter.
+ *
+ * @return
+ */
+ boolean isPageable() {
+
+ return Pageable.class.isAssignableFrom(type);
+ }
+
+
+ /**
+ * Returns whether the {@link Parameter} is a {@link Sort} parameter.
+ *
+ * @return
+ */
+ boolean isSort() {
+
+ return Sort.class.isAssignableFrom(type);
+ }
+}
diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/ParameterOutOfBoundsException.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/ParameterOutOfBoundsException.java
new file mode 100644
index 000000000..62c8156da
--- /dev/null
+++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/ParameterOutOfBoundsException.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2008-2010 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.query;
+
+/**
+ * Exception to be thrown when trying to access a {@link Parameter} with an
+ * invalid index inside a {@link Parameters} instance.
+ *
+ * @author Oliver Gierke
+ */
+public class ParameterOutOfBoundsException extends RuntimeException {
+
+ private static final long serialVersionUID = 8433209953653278886L;
+
+
+ /**
+ * Creates a new {@link ParameterOutOfBoundsException} with the given
+ * exception as cause.
+ *
+ * @param cause
+ */
+ public ParameterOutOfBoundsException(Throwable cause) {
+
+ super(cause);
+ }
+}
diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/Parameters.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/Parameters.java
new file mode 100644
index 000000000..5a75c4271
--- /dev/null
+++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/Parameters.java
@@ -0,0 +1,337 @@
+/*
+ * Copyright 2008-2010 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.query;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
+import org.springframework.core.ParameterNameDiscoverer;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.util.Assert;
+
+
+/**
+ * Abstracts method parameters that have to be bound to query parameters or
+ * applied to the query independently.
+ *
+ * @author Oliver Gierke
+ */
+public final class Parameters implements Iterable {
+
+ @SuppressWarnings("unchecked")
+ public static final List> TYPES = Arrays.asList(Pageable.class,
+ Sort.class);
+
+ private static final String ALL_OR_NOTHING =
+ String.format(
+ "Either use @%s "
+ + "on all parameters except %s and %s typed once, or none at all!",
+ Param.class.getSimpleName(),
+ Pageable.class.getSimpleName(), Sort.class.getSimpleName());
+
+ private final int pageableIndex;
+ private final int sortIndex;
+
+ private final List parameters;
+ private final ParameterNameDiscoverer discoverer =
+ new LocalVariableTableParameterNameDiscoverer();
+
+
+ /**
+ * Creates a new instance of {@link Parameters}.
+ *
+ * @param method
+ */
+ public Parameters(Method method) {
+
+ Assert.notNull(method);
+
+ this.parameters = new ArrayList();
+
+ List> types = Arrays.asList(method.getParameterTypes());
+
+ for (int i = 0; i < types.size(); i++) {
+ String name = getParameterName(method, i);
+ parameters.add(new Parameter(types.get(i), this, i, name));
+ }
+
+ this.pageableIndex = types.indexOf(Pageable.class);
+ this.sortIndex = types.indexOf(Sort.class);
+
+ assertEitherAllParamAnnotatedOrNone();
+ }
+
+
+ /**
+ * Returns the name of the parameter of the given {@link Method} with the
+ * given index. Inspects {@link Param} annotation before falling back to a
+ * {@link ParameterNameDiscoverer}.
+ *
+ * @param method
+ * @param index
+ * @return
+ */
+ private String getParameterName(Method method, int index) {
+
+ for (Annotation annotation : method.getParameterAnnotations()[index]) {
+ if (annotation instanceof Param) {
+ return ((Param) annotation).value();
+ }
+ }
+
+ String[] parameterNames = discoverer.getParameterNames(method);
+
+ if (parameterNames != null) {
+ return parameterNames[index];
+ }
+
+ return null;
+ }
+
+
+ /**
+ * Creates a new {@link Parameters} instance with the given
+ * {@link Parameter}s put into new context.
+ *
+ * @param originals
+ */
+ private Parameters(List originals) {
+
+ this.parameters = new ArrayList();
+
+ int pageableIndexTemp = -1;
+ int sortIndexTemp = -1;
+
+ for (int i = 0; i < originals.size(); i++) {
+
+ Parameter original = originals.get(i);
+
+ this.parameters.add(new Parameter(original, this, i));
+
+ pageableIndexTemp = original.isPageable() ? i : -1;
+ sortIndexTemp = original.isSort() ? i : -1;
+ }
+
+ this.pageableIndex = pageableIndexTemp;
+ this.sortIndex = sortIndexTemp;
+ }
+
+
+ /**
+ * Returns whether the method the {@link Parameters} was created for
+ * contains a {@link Pageable} argument.
+ *
+ * @return
+ */
+ public boolean hasPageableParameter() {
+
+ return pageableIndex != -1;
+ }
+
+
+ /**
+ * Returns the index of the {@link Pageable} {@link Method} parameter if
+ * available. Will return {@literal -1} if there is no {@link Pageable}
+ * argument in the {@link Method}'s parameter list.
+ *
+ * @return the pageableIndex
+ */
+ public int getPageableIndex() {
+
+ return pageableIndex;
+ }
+
+
+ /**
+ * Returns the index of the {@link Sort} {@link Method} parameter if
+ * available. Will return {@literal -1} if there is no {@link Sort} argument
+ * in the {@link Method}'s parameter list.
+ *
+ * @return
+ */
+ public int getSortIndex() {
+
+ return sortIndex;
+ }
+
+
+ /**
+ * Returns whether the method the {@link Parameters} was created for
+ * contains a {@link Sort} argument.
+ *
+ * @return
+ */
+ public boolean hasSortParameter() {
+
+ return sortIndex != -1;
+ }
+
+
+ /**
+ * Returns the parameter with the given index.
+ *
+ * @param index
+ * @return
+ */
+ public Parameter getParameter(int index) {
+
+ try {
+ return parameters.get(index);
+ } catch (IndexOutOfBoundsException e) {
+ throw new ParameterOutOfBoundsException(e);
+ }
+ }
+
+
+ /**
+ * Returns whether we have a parameter at the given position.
+ *
+ * @param position
+ * @return
+ */
+ public boolean hasParameterAt(int position) {
+
+ try {
+ return null != getParameter(position);
+ } catch (ParameterOutOfBoundsException e) {
+ return false;
+ }
+ }
+
+
+ /**
+ * Returns whether the method signature contains one of the special
+ * parameters ({@link Pageable}, {@link Sort}).
+ *
+ * @return
+ */
+ public boolean hasSpecialParameter() {
+
+ return hasSortParameter() || hasPageableParameter();
+ }
+
+
+ /**
+ * Returns the number of parameters.
+ *
+ * @return
+ */
+ public int getNumberOfParameters() {
+
+ return parameters.size();
+ }
+
+
+ /**
+ * Returns a {@link Parameters} instance with effectively all special
+ * parameters removed.
+ *
+ * @see Parameter#TYPES
+ * @see Parameter#isSpecialParameter()
+ * @return
+ */
+ public Parameters getBindableParameters() {
+
+ List bindables = new ArrayList();
+
+ for (Parameter candidate : this) {
+
+ if (candidate.isBindable()) {
+ bindables.add(candidate);
+ }
+ }
+
+ return new Parameters(bindables);
+ }
+
+
+ /**
+ * Returns the index of the placeholder inside a query for the parameter
+ * with the given index. They might differ from the parameter index as the
+ * method signature can contain special parameters (e.g. {@link Sort},
+ * {@link Pageable}) that are not bound as plain query parameters but rather
+ * handled differently.
+ *
+ * @param index
+ * @return the placeholder postion for the parameter with the given index.
+ * Will return 0 for special parameters.
+ */
+ int getPlaceholderPosition(Parameter parameter) {
+
+ return parameter.isSpecialParameter() ? 0
+ : getPlaceholderPositionRecursively(parameter);
+ }
+
+
+ private int getPlaceholderPositionRecursively(Parameter parameter) {
+
+ int result = parameter.isSpecialParameter() ? 0 : 1;
+
+ return parameter.isFirst() ? result : result
+ + getPlaceholderPositionRecursively(parameter.getPrevious());
+ }
+
+
+ /**
+ * Asserts that either all of the non special parameters ({@link Pageable},
+ * {@link Sort}) are annotated with {@link Param} or none of them is.
+ *
+ * @param method
+ */
+ private void assertEitherAllParamAnnotatedOrNone() {
+
+ boolean nameFound = false;
+
+ for (Parameter parameter : this.getBindableParameters()) {
+
+ if (parameter.isNamedParameter()) {
+ Assert.isTrue(nameFound || parameter.isFirst(), ALL_OR_NOTHING);
+ nameFound = true;
+ } else {
+ Assert.isTrue(!nameFound, ALL_OR_NOTHING);
+ }
+ }
+ }
+
+
+ /**
+ * Returns whether the given type is a bindable parameter.
+ *
+ * @param type
+ * @return
+ */
+ public static boolean isBindable(Class> type) {
+
+ return !TYPES.contains(type);
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Iterable#iterator()
+ */
+ public Iterator iterator() {
+
+ return parameters.iterator();
+ }
+}
\ No newline at end of file
diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/QueryCreationException.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/QueryCreationException.java
new file mode 100644
index 000000000..1e52aa369
--- /dev/null
+++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/QueryCreationException.java
@@ -0,0 +1,70 @@
+package org.springframework.data.repository.query;
+
+/**
+ * Exception to be thrown if a query cannot be created from a
+ * {@link QueryMethod}.
+ *
+ * @author Oliver Gierke
+ */
+public final class QueryCreationException extends RuntimeException {
+
+ private static final long serialVersionUID = -1238456123580L;
+ private static final String MESSAGE_TEMPLATE =
+ "Could not create query for method %s! Could not find property %s on domain class %s.";
+
+
+ /**
+ * Creates a new {@link QueryCreationException}.
+ *
+ * @param method
+ */
+ private QueryCreationException(String message) {
+
+ super(message);
+ }
+
+
+ /**
+ * Rejects the given domain class property.
+ *
+ * @param method
+ * @param propertyName
+ * @return
+ */
+ public static QueryCreationException invalidProperty(QueryMethod method,
+ String propertyName) {
+
+ return new QueryCreationException(String.format(MESSAGE_TEMPLATE,
+ method, propertyName, method.getDomainClass().getName()));
+ }
+
+
+ /**
+ * Creates a new {@link QueryCreationException}.
+ *
+ * @param method
+ * @param message
+ * @return
+ */
+ public static QueryCreationException create(QueryMethod method,
+ String message) {
+
+ return new QueryCreationException(String.format(
+ "Could not create query for %s! Reason: %s", method, message));
+ }
+
+
+ /**
+ * Creates a new {@link QueryCreationException} for the given
+ * {@link QueryMethod} and {@link Throwable} as cause.
+ *
+ * @param method
+ * @param cause
+ * @return
+ */
+ public static QueryCreationException create(QueryMethod method,
+ Throwable cause) {
+
+ return create(method, cause.getMessage());
+ }
+}
diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/QueryLookupStrategy.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/QueryLookupStrategy.java
new file mode 100644
index 000000000..5c21ab50d
--- /dev/null
+++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/QueryLookupStrategy.java
@@ -0,0 +1,44 @@
+package org.springframework.data.repository.query;
+
+import java.util.Locale;
+
+import org.springframework.util.StringUtils;
+
+
+/**
+ * Strategy interface for which way to lookup {@link RepositoryQuery}s.
+ *
+ * @author Oliver Gierke
+ */
+public interface QueryLookupStrategy {
+
+ public static enum Key {
+
+ CREATE, USE_DECLARED_QUERY, CREATE_IF_NOT_FOUND;
+
+ /**
+ * Returns a strategy key from the given XML value.
+ *
+ * @param xml
+ * @return a strategy key from the given XML value
+ */
+ public static Key create(String xml) {
+
+ if (!StringUtils.hasText(xml)) {
+ return null;
+ }
+
+ return valueOf(xml.toUpperCase(Locale.US).replace("-", "_"));
+ }
+ }
+
+
+ /**
+ * Resolves a {@link RepositoryQuery} from the given {@link QueryMethod}
+ * that can be executed afterwards.
+ *
+ * @param method
+ * @return
+ */
+ RepositoryQuery resolveQuery(QueryMethod method);
+}
\ No newline at end of file
diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/QueryMethod.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/QueryMethod.java
new file mode 100644
index 000000000..94e53139f
--- /dev/null
+++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/QueryMethod.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2008-2010 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.query;
+
+import static org.springframework.data.repository.util.ClassUtils.*;
+
+import java.lang.reflect.Method;
+import java.util.List;
+
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.util.Assert;
+
+
+/**
+ * Abstraction of a method that is designated to execute a finder query.
+ * Enriches the standard {@link Method} interface with Hades specific
+ * information that is necessary to construct {@link HadesQuery}s for the
+ * method.
+ *
+ * @author Oliver Gierke
+ */
+public class QueryMethod {
+
+ private final Method method;
+ private final Parameters parameters;
+
+
+ /**
+ * Creates a new {@link QueryMethod} from the given parameters. Looks up the
+ * correct query to use for following invocations of the method given.
+ *
+ * @param method must not be {@literal null}
+ */
+ public QueryMethod(Method method) {
+
+ Assert.notNull(method, "Method must not be null!");
+
+ for (Class> type : Parameters.TYPES) {
+ if (getNumberOfOccurences(method, type) > 1) {
+ throw new IllegalStateException(String.format(
+ "Method must only one argument of type %s!",
+ type.getSimpleName()));
+ }
+ }
+
+ if (hasParameterOfType(method, Pageable.class)) {
+ assertReturnType(method, Page.class, List.class);
+ if (hasParameterOfType(method, Sort.class)) {
+ throw new IllegalStateException(
+ "Method must not have Pageable *and* Sort parameter. "
+ + "Use sorting capabilities on Pageble instead!");
+ }
+ }
+
+ this.method = method;
+ this.parameters = new Parameters(method);
+ }
+
+
+ /**
+ * Returns the method's name.
+ *
+ * @return
+ */
+ public String getName() {
+
+ return method.getName();
+ }
+
+
+ /**
+ * Returns whether the given
+ *
+ * @param number
+ * @return
+ */
+ public boolean isCorrectNumberOfParameters(int number) {
+
+ return number == parameters.getBindableParameters()
+ .getNumberOfParameters();
+ }
+
+
+ /**
+ * Returns the domain class for this method.
+ *
+ * @return
+ */
+ public Class> getDomainClass() {
+
+ return getReturnedDomainClass(method);
+ }
+
+
+ /**
+ * Returns whether the finder will actually return a collection of entities
+ * or a single one.
+ *
+ * @return
+ */
+ protected boolean isCollectionQuery() {
+
+ Class> returnType = method.getReturnType();
+ return org.springframework.util.ClassUtils.isAssignable(List.class,
+ returnType);
+ }
+
+
+ /**
+ * Returns whether the finder will return a {@link Page} of results.
+ *
+ * @return
+ */
+ protected boolean isPageQuery() {
+
+ Class> returnType = method.getReturnType();
+ return org.springframework.util.ClassUtils.isAssignable(Page.class,
+ returnType);
+ }
+
+
+ /**
+ * Returns the {@link Parameters} wrapper to gain additional information
+ * about {@link Method} parameters.
+ *
+ * @return
+ */
+ public Parameters getParameters() {
+
+ return parameters;
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+
+ return method.toString();
+ }
+}
diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/RepositoryQuery.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/RepositoryQuery.java
new file mode 100644
index 000000000..2f4524f10
--- /dev/null
+++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/RepositoryQuery.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2008-2010 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.query;
+
+/**
+ * Interface for a query abstraction.
+ *
+ * @author Oliver Gierke
+ */
+public interface RepositoryQuery {
+
+ /**
+ * Executes the {@link RepositoryQuery} with the given parameters.
+ *
+ * @param store
+ * @param parameters
+ * @return
+ */
+ public Object execute(Object[] parameters);
+}
\ No newline at end of file
diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/parser/OrderBySource.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/parser/OrderBySource.java
new file mode 100644
index 000000000..7c5a63e43
--- /dev/null
+++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/parser/OrderBySource.java
@@ -0,0 +1,70 @@
+package org.springframework.data.repository.query.parser;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.springframework.data.domain.Sort;
+import org.springframework.data.domain.Sort.Direction;
+import org.springframework.data.domain.Sort.Order;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+
+/**
+ * Simple helper class to create a {@link Sort} instance from a method name end.
+ * It expects the last part of the method name to be given and supports lining
+ * up multiple properties ending with the sorting direction. So the following
+ * method ends are valid: {@code LastnameUsernameDesc},
+ * {@code LastnameAscUsernameDesc}.
+ *
+ * @author Oliver Gierke
+ */
+public class OrderBySource {
+
+ private final List orders;
+
+
+ public OrderBySource(String clause) {
+
+ this.orders = new ArrayList();
+ List properties = new ArrayList();
+
+ for (String part : clause.split("(?<=[a-z])(?=[A-Z])")) {
+
+ Direction direction = defaultedFrom(part);
+
+ if (direction == null) {
+ properties.add(StringUtils.uncapitalize(part));
+ } else {
+ Assert.notEmpty(
+ properties,
+ "Invalid order syntax! You have to provide at least one property before the sort direction.");
+ orders.addAll(Order.create(direction, properties));
+ properties.clear();
+ }
+ }
+ }
+
+
+ /**
+ * Tries to resolve a {@link Direction} for the given {@link String}.
+ * Returns {@literal null} if resolving fails.
+ *
+ * @param candidate
+ * @return
+ */
+ private Direction defaultedFrom(String candidate) {
+
+ try {
+ return Direction.fromString(candidate);
+ } catch (IllegalArgumentException e) {
+ return null;
+ }
+ }
+
+
+ public Sort toSort() {
+
+ return new Sort(this.orders);
+ }
+}
\ No newline at end of file
diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/parser/Part.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/parser/Part.java
new file mode 100644
index 000000000..75eac7733
--- /dev/null
+++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/parser/Part.java
@@ -0,0 +1,221 @@
+package org.springframework.data.repository.query.parser;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.springframework.data.repository.util.ClassUtils;
+import org.springframework.util.StringUtils;
+
+
+/**
+ * A single part of a method name that has to be transformed into a query part.
+ * The actual transformation is defined by a {@link Type} that is determined
+ * from inspecting the given part. The query part can then be looked up via
+ * {@link #getQueryPart()}.
+ *
+ * @author Oliver Gierke
+ */
+public class Part {
+
+ private final String property;
+ private final Part.Type type;
+
+
+ /**
+ * Creates a new {@link Part} from the given method name part, the
+ * {@link Class} the part originates from and the start parameter index.
+ *
+ * @param part
+ * @param clazz
+ */
+ public Part(String part, Class> clazz) {
+
+ this.type = Type.fromProperty(part, clazz);
+ this.property = type.extractProperty(part);
+ }
+
+
+ public boolean getParameterRequired() {
+
+ return getNumberOfArguments() > 0;
+ }
+
+
+ /**
+ * Returns how many method parameters are bound by this part.
+ *
+ * @return
+ */
+ public int getNumberOfArguments() {
+
+ return type.getNumberOfArguments();
+ }
+
+
+ /**
+ * @return the part
+ */
+ public String getProperty() {
+
+ return property;
+ }
+
+
+ /**
+ * @return the type
+ */
+ public Part.Type getType() {
+
+ return type;
+ }
+
+ /**
+ * The type of a method name part. Used to create query parts in various
+ * ways.
+ *
+ * @author Oliver Gierke
+ */
+ public static enum Type {
+
+ BETWEEN(null, 2, "Between"),
+
+ IS_NOT_NULL(null, 0, "IsNotNull", "NotNull"),
+
+ IS_NULL(null, 0, "IsNull", "Null"),
+
+ LESS_THAN("<", "LessThan"),
+
+ GREATER_THAN(">", "GreaterThan"),
+
+ NOT_LIKE("not like", "NotLike"),
+
+ LIKE("like", "Like"),
+
+ NEGATING_SIMPLE_PROPERTY("<>", "Not"),
+
+ SIMPLE_PROPERTY("=");
+
+ // Need to list them again explicitly as the order is important
+ // (esp. for IS_NULL, IS_NOT_NULL)
+ private static final List ALL = Arrays.asList(IS_NOT_NULL,
+ IS_NULL, BETWEEN, LESS_THAN, GREATER_THAN, NOT_LIKE, LIKE,
+ NEGATING_SIMPLE_PROPERTY, SIMPLE_PROPERTY);
+ private List keywords;
+ private String operator;
+ private int numberOfArguments;
+
+
+ /**
+ * Creates a new {@link Type} using the given keyword, number of
+ * arguments to be bound and operator. Keyword and operator can be
+ * {@literal null}.
+ *
+ * @param operator
+ * @param numberOfArguments
+ * @param keywords
+ */
+ private Type(String operator, int numberOfArguments, String... keywords) {
+
+ this.operator = operator;
+ this.numberOfArguments = numberOfArguments;
+ this.keywords = Arrays.asList(keywords);
+ }
+
+
+ private Type(String operator, String... keywords) {
+
+ this(operator, 1, keywords);
+ }
+
+
+ /**
+ * Returns the {@link Type} of the {@link Part} for the given raw
+ * property and the given {@link Class}. This will try to detect e.g.
+ * keywords contained in the raw property that trigger special query
+ * creation. Returns {@link #SIMPLE_PROPERTY} by default.
+ *
+ * @param rawProperty
+ * @param clazz
+ * @return
+ */
+ public static Part.Type fromProperty(String rawProperty, Class> clazz) {
+
+ for (Part.Type type : ALL) {
+ if (type.supports(rawProperty, clazz)) {
+ return type;
+ }
+ }
+
+ return SIMPLE_PROPERTY;
+ }
+
+
+ public String getOperator() {
+
+ return this.operator;
+ }
+
+
+ /**
+ * Returns whether the the type supports the given raw property. Default
+ * implementation checks whether the property ends with the registered
+ * keyword. Does not support the keyword if the property is a valid
+ * field as is.
+ *
+ * @param property
+ * @param clazz
+ * @return
+ */
+ protected boolean supports(String property, Class> clazz) {
+
+ if (keywords == null) {
+ return true;
+ }
+
+ if (ClassUtils.hasProperty(clazz, property)) {
+ return false;
+ }
+
+ for (String keyword : keywords) {
+ if (property.endsWith(keyword)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+
+ /**
+ * Returns the number of arguments the property binds. By default this
+ * exactly one argument.
+ *
+ * @return
+ */
+ public int getNumberOfArguments() {
+
+ return numberOfArguments;
+ }
+
+
+ /**
+ * Callback method to extract the actual property to be bound from the
+ * given part. Strips the keyword from the part's end if available.
+ *
+ * @param part
+ * @return
+ */
+ public String extractProperty(String part) {
+
+ String candidate = StringUtils.uncapitalize(part);
+
+ for (String keyword : keywords) {
+ if (candidate.endsWith(keyword)) {
+ return candidate.substring(0, candidate.indexOf(keyword));
+ }
+ }
+
+ return candidate;
+ }
+ }
+}
\ No newline at end of file
diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/parser/PartSource.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/parser/PartSource.java
new file mode 100644
index 000000000..ea81acdf5
--- /dev/null
+++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/parser/PartSource.java
@@ -0,0 +1,125 @@
+package org.springframework.data.repository.query.parser;
+
+import static java.lang.String.*;
+import static java.util.regex.Pattern.*;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.regex.Pattern;
+
+
+/**
+ * Helper class to split a method name into all of its logical parts (prefix,
+ * properties, postfix).
+ *
+ * @author Oliver Gierke
+ */
+public class PartSource {
+
+ private static final String ORDER_BY = "OrderBy";
+ private static final String[] PREFIXES = new String[] { "findBy", "find",
+ "readBy", "read", "getBy", "get" };
+ private static final String PREFIX_TEMPLATE = "^%s(?=[A-Z]).*";
+ private static final String KEYWORD_TEMPLATE = "(%s)(?=[A-Z])";
+
+ private final String cleanedUpString;
+ private final OrderBySource orderBySource;
+
+
+ public PartSource(String methodName) {
+
+ String removedPrefixes = strip(methodName);
+
+ String[] parts = split(removedPrefixes, ORDER_BY);
+
+ if (parts.length > 2) {
+ throw new IllegalArgumentException(
+ "OrderBy must not be used more than once in a method name!");
+ }
+
+ this.cleanedUpString = parts[0];
+ this.orderBySource =
+ parts.length == 2 ? getOrderBySourceFor(parts[1]) : null;
+ }
+
+
+ public OrderBySource getOrderBySource() {
+
+ return orderBySource;
+ }
+
+
+ public boolean hasOrderByClause() {
+
+ return orderBySource != null;
+ }
+
+
+ protected OrderBySource getOrderBySourceFor(String postfix) {
+
+ return new OrderBySource(postfix);
+ }
+
+
+ /**
+ * Returns an iterator over all the {@link PartSource}s created by spliting
+ * up the current one with the given keyword.
+ *
+ * @param keyword
+ * @return
+ */
+ public Iterator getParts(String keyword) {
+
+ List parts = new ArrayList();
+ for (String part : split(cleanedUpString, keyword)) {
+ parts.add(new PartSource(part));
+ }
+
+ return parts.iterator();
+ }
+
+
+ public String cleanedUp() {
+
+ return cleanedUpString;
+ }
+
+
+ /**
+ * Strips a prefix from the given method name if it starts with one of
+ * {@value #PREFIXES}.
+ *
+ * @param methodName
+ * @return
+ */
+ private String strip(String methodName) {
+
+ for (String prefix : PREFIXES) {
+
+ String regex = format(PREFIX_TEMPLATE, prefix);
+ if (methodName.matches(regex)) {
+ return methodName.substring(prefix.length());
+ }
+ }
+
+ return methodName;
+ }
+
+
+ /**
+ * Splits the given text at the given keywords. Expects camelcase style to
+ * only match concrete keywords and not derivatives of it.
+ *
+ * @param text
+ * @param keyword
+ * @return
+ */
+ private String[] split(String text, String keyword) {
+
+ String regex = format(KEYWORD_TEMPLATE, keyword);
+
+ Pattern pattern = compile(regex);
+ return pattern.split(text);
+ }
+}
\ No newline at end of file
diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/IdAware.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/IdAware.java
new file mode 100644
index 000000000..d9d7a2149
--- /dev/null
+++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/IdAware.java
@@ -0,0 +1,17 @@
+package org.springframework.data.repository.support;
+
+/**
+ * Interface to abstract the ways to retrieve the id of the given entity.
+ *
+ * @author Oliver Gierke
+ */
+public interface IdAware {
+
+ /**
+ * Returns the id of the given entity.
+ *
+ * @param entity
+ * @return
+ */
+ Object getId(Object entity);
+}
\ No newline at end of file
diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/IsNewAware.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/IsNewAware.java
new file mode 100644
index 000000000..cf1c7c6bc
--- /dev/null
+++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/IsNewAware.java
@@ -0,0 +1,18 @@
+package org.springframework.data.repository.support;
+
+/**
+ * Interface to abstract the ways to determine if the given entity is to be
+ * considered as new.
+ *
+ * @author Oliver Gierke
+ */
+public interface IsNewAware {
+
+ /**
+ * Returns whether the given entity is considered to be new.
+ *
+ * @param entity
+ * @return
+ */
+ boolean isNew(Object entity);
+}
\ No newline at end of file
diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/PersistableEntityInformation.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/PersistableEntityInformation.java
new file mode 100644
index 000000000..b406cf99d
--- /dev/null
+++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/PersistableEntityInformation.java
@@ -0,0 +1,37 @@
+package org.springframework.data.repository.support;
+
+import org.springframework.data.domain.Persistable;
+
+
+/**
+ * Implementation of {@link IsNewAware} that assumes the entity handled
+ * implements {@link Persistable} and uses {@link Persistable#isNew()} for the
+ * {@link #isNew(Object)} check.
+ *
+ * @author Oliver Gierke
+ */
+public class PersistableEntityInformation implements IsNewAware, IdAware {
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.synyx.hades.dao.orm.GenericJpaDao.IsNewStrategy#isNew(java.lang
+ * .Object)
+ */
+ public boolean isNew(Object entity) {
+
+ return ((Persistable>) entity).isNew();
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.synyx.hades.dao.orm.GenericDaoSupport.IdAware#getId(java.lang
+ * .Object)
+ */
+ public Object getId(Object entity) {
+
+ return ((Persistable>) entity).getId();
+ }
+}
\ No newline at end of file
diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/ReflectiveEntityInformationSupport.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/ReflectiveEntityInformationSupport.java
new file mode 100644
index 000000000..c6b39da9a
--- /dev/null
+++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/ReflectiveEntityInformationSupport.java
@@ -0,0 +1,131 @@
+package org.springframework.data.repository.support;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+import org.springframework.util.Assert;
+import org.springframework.util.ReflectionUtils;
+import org.springframework.util.ReflectionUtils.FieldCallback;
+import org.springframework.util.ReflectionUtils.MethodCallback;
+
+
+/**
+ * {@link IsNewAware} and {@link IdAware} implementation that reflectively
+ * checks a {@link Field} or {@link Method} annotated with the given
+ * annotations. Subclasses usually simply have to provide the persistence
+ * technology specific annotations.
+ *
+ * @author Oliver Gierke
+ */
+public class ReflectiveEntityInformationSupport implements IsNewAware, IdAware {
+
+ private Field field;
+ private Method method;
+
+
+ /**
+ * Creates a new {@link ReflectiveEntityInformationSupport} by inspecting
+ * the given class for a {@link Field} or {@link Method} for and {@link Id}
+ * annotation.
+ *
+ * @param domainClass not {@literal null}, must be annotated with
+ * {@link Entity} and carry an anootation defining the id
+ * property.
+ */
+ public ReflectiveEntityInformationSupport(Class> domainClass,
+ final Class extends Annotation>... annotationsToScanFor) {
+
+ Assert.notNull(domainClass);
+
+ ReflectionUtils.doWithFields(domainClass, new FieldCallback() {
+
+ public void doWith(Field field) {
+
+ if (ReflectiveEntityInformationSupport.this.field != null) {
+ return;
+ }
+
+ if (hasAnnotation(field, annotationsToScanFor)) {
+ ReflectiveEntityInformationSupport.this.field = field;
+ }
+ }
+ });
+
+ if (field != null) {
+ return;
+ }
+
+ ReflectionUtils.doWithMethods(domainClass, new MethodCallback() {
+
+ public void doWith(Method method) {
+
+ if (ReflectiveEntityInformationSupport.this.method != null) {
+ return;
+ }
+
+ if (hasAnnotation(method, annotationsToScanFor)) {
+ ReflectiveEntityInformationSupport.this.method = method;
+ }
+ }
+ });
+
+ Assert.isTrue(this.field != null || this.method != null,
+ "No id method or field found!");
+ }
+
+
+ /**
+ * Checks whether the given {@link AnnotatedElement} carries one of the
+ * given {@link Annotation}s.
+ *
+ * @param annotatedElement
+ * @param annotations
+ * @return
+ */
+ private boolean hasAnnotation(AnnotatedElement annotatedElement,
+ Class extends Annotation>... annotations) {
+
+ for (Class extends Annotation> annotation : annotations) {
+
+ if (annotatedElement.getAnnotation(annotation) != null) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.springframework.data.repository.support.RepositorySupport.IsNewAware
+ * #isNew(java.lang.Object)
+ */
+ public boolean isNew(Object entity) {
+
+ return getId(entity) == null;
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.springframework.data.repository.support.RepositorySupport.IdAware
+ * #getId(java.lang.Object)
+ */
+ public Object getId(Object entity) {
+
+ if (field != null) {
+ ReflectionUtils.makeAccessible(field);
+ return ReflectionUtils.getField(field, entity);
+ }
+
+ ReflectionUtils.makeAccessible(method);
+ return ReflectionUtils.invokeMethod(method, entity);
+ }
+}
\ No newline at end of file
diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/RepositoryFactoryBeanSupport.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/RepositoryFactoryBeanSupport.java
new file mode 100644
index 000000000..6c6dbaa13
--- /dev/null
+++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/RepositoryFactoryBeanSupport.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2008-2010 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.support;
+
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.BeanFactoryAware;
+import org.springframework.beans.factory.FactoryBean;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.ListableBeanFactory;
+import org.springframework.beans.factory.annotation.Required;
+import org.springframework.data.repository.Repository;
+import org.springframework.data.repository.query.QueryLookupStrategy.Key;
+import org.springframework.data.repository.util.TxUtils;
+import org.springframework.util.Assert;
+
+
+/**
+ * Adapter for Springs {@link FactoryBean} interface to allow easy setup of
+ * repository factories via Spring configuration.
+ *
+ * @author Oliver Gierke
+ * @param the type of the repository
+ */
+public abstract class RepositoryFactoryBeanSupport>
+ implements FactoryBean, InitializingBean, BeanFactoryAware {
+
+ private RepositoryFactorySupport factory;
+
+ private Key queryLookupStrategyKey;
+ private Class extends T> repositoryInterface;
+ private Object customImplementation;
+
+ private String transactionManagerName = TxUtils.DEFAULT_TRANSACTION_MANAGER;
+ private RepositoryProxyPostProcessor txPostProcessor;
+
+
+ /**
+ * Setter to inject the repository interface to implement.
+ *
+ * @param repositoryInterface the repository interface to set
+ */
+ @Required
+ public void setRepositoryInterface(Class repositoryInterface) {
+
+ Assert.notNull(repositoryInterface);
+ this.repositoryInterface = repositoryInterface;
+ }
+
+
+ public void setQueryLookupStrategyKey(Key queryLookupStrategyKey) {
+
+ this.queryLookupStrategyKey = queryLookupStrategyKey;
+ }
+
+
+ /**
+ * Setter to configure which transaction manager to be used. We have to use
+ * the bean name explicitly as otherwise the qualifier of the
+ * {@link org.springframework.transaction.annotation.Transactional}
+ * annotation is used. By explicitly defining the transaction manager bean
+ * name we favour let this one be the default one chosen.
+ *
+ * @param transactionManager
+ */
+ public void setTransactionManager(String transactionManager) {
+
+ this.transactionManagerName =
+ transactionManager == null ? TxUtils.DEFAULT_TRANSACTION_MANAGER
+ : transactionManager;
+ }
+
+
+ /**
+ * Setter to inject a custom repository implementation.
+ *
+ * @param customImplementation
+ */
+ public void setCustomImplementation(Object customImplementation) {
+
+ this.customImplementation = customImplementation;
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.springframework.beans.factory.FactoryBean#getObject()
+ */
+ public T getObject() {
+
+ return factory.getRepository(repositoryInterface,
+ customImplementation);
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.springframework.beans.factory.FactoryBean#getObjectType()
+ */
+ @SuppressWarnings("unchecked")
+ public Class extends T> getObjectType() {
+
+ return (Class extends T>) (null == repositoryInterface ? Repository.class
+ : repositoryInterface);
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.springframework.beans.factory.FactoryBean#isSingleton()
+ */
+ public boolean isSingleton() {
+
+ return true;
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
+ */
+ public void afterPropertiesSet() {
+
+ this.factory = createRepositoryFactory();
+ this.factory.setQueryLookupStrategyKey(queryLookupStrategyKey);
+ this.factory.validate(repositoryInterface,
+ customImplementation);
+ this.factory.addDaoProxyPostProcessor(txPostProcessor);
+ }
+
+
+ /**
+ * Create the actual {@link RepositoryFactorySupport} instance.
+ *
+ * @return
+ */
+ protected abstract RepositoryFactorySupport createRepositoryFactory();
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.springframework.beans.factory.BeanFactoryAware#setBeanFactory(org
+ * .springframework.beans.factory.BeanFactory)
+ */
+ public void setBeanFactory(BeanFactory beanFactory) {
+
+ Assert.isInstanceOf(ListableBeanFactory.class, beanFactory);
+
+ this.txPostProcessor =
+ new TransactionalRepositoryProxyPostProcessor(
+ (ListableBeanFactory) beanFactory,
+ transactionManagerName);
+ }
+}
diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/RepositoryFactorySupport.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/RepositoryFactorySupport.java
new file mode 100644
index 000000000..99626d0ac
--- /dev/null
+++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/RepositoryFactorySupport.java
@@ -0,0 +1,458 @@
+/*
+ * Copyright 2008-2010 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.support;
+
+import static org.springframework.data.repository.util.ClassUtils.*;
+import static org.springframework.util.ReflectionUtils.*;
+
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.aopalliance.intercept.MethodInterceptor;
+import org.aopalliance.intercept.MethodInvocation;
+import org.springframework.aop.framework.ProxyFactory;
+import org.springframework.data.repository.Repository;
+import org.springframework.data.repository.query.QueryLookupStrategy;
+import org.springframework.data.repository.query.QueryLookupStrategy.Key;
+import org.springframework.data.repository.query.QueryMethod;
+import org.springframework.data.repository.query.RepositoryQuery;
+import org.springframework.data.repository.util.ClassUtils;
+import org.springframework.util.Assert;
+
+
+/**
+ * Factory bean to create instances of a given repository interface. Creates a
+ * proxy implementing the configured repository interface and apply an advice
+ * handing the control to the {@code QueryExecuterMethodInterceptor}. Query
+ * detection strategy can be configured by setting
+ * {@link QueryLookupStrategy.Key}.
+ *
+ * @author Oliver Gierke
+ */
+public abstract class RepositoryFactorySupport {
+
+ private QueryLookupStrategy.Key queryLookupStrategyKey;
+
+ private final Map methodCache =
+ new ConcurrentHashMap();
+ private final List postProcessors =
+ new ArrayList();
+
+
+ /**
+ * Sets the strategy of how to lookup a query to execute finders.
+ *
+ * @param queryLookupStrategy the createFinderQueries to set
+ */
+ public void setQueryLookupStrategyKey(Key key) {
+
+ this.queryLookupStrategyKey = key;
+ }
+
+
+ /**
+ * Adds {@link RepositoryProxyPostProcessor}s to the factory to allow
+ * manipulation of the {@link ProxyFactory} before the proxy gets created.
+ * Note that the {@link QueryExecuterMethodInterceptor} will be added to the
+ * proxy after the {@link RepositoryProxyPostProcessor}s are
+ * considered.
+ *
+ * @param processor
+ */
+ protected void addDaoProxyPostProcessor(
+ RepositoryProxyPostProcessor processor) {
+
+ Assert.notNull(processor);
+ this.postProcessors.add(processor);
+ }
+
+
+ /**
+ * Returns a repository instance for the given interface.
+ *
+ * @param
+ * @param repositoryInterface
+ * @return
+ */
+ public > T getRepository(
+ Class repositoryInterface) {
+
+ return getRepository(repositoryInterface, null);
+ }
+
+
+ /**
+ * Returns a repository instance for the given interface backed by an
+ * instance providing implementation logic for custom logic.
+ *
+ * @param
+ * @param repositoryInterface
+ * @param customDaoImplementation
+ * @return
+ */
+ @SuppressWarnings("unchecked")
+ public > T getRepository(
+ Class repositoryInterface, Object customDaoImplementation) {
+
+ validate(repositoryInterface, customDaoImplementation);
+
+ Class> domainClass = getDomainClass(repositoryInterface);
+ RepositorySupport, ?> target =
+ getTargetRepository(domainClass);
+
+ // Create proxy
+ ProxyFactory result = new ProxyFactory();
+ result.setTarget(target);
+ result.setInterfaces(new Class[] { repositoryInterface });
+
+ for (RepositoryProxyPostProcessor processor : postProcessors) {
+ processor.postProcess(result);
+ }
+
+ result.addAdvice(new QueryExecuterMethodInterceptor(
+ repositoryInterface, customDaoImplementation, target));
+
+ return (T) result.getProxy();
+ }
+
+
+ /**
+ * Create a {@link RepositorySupport} instance as backing for the
+ * query proxy.
+ *
+ * @param
+ * @param domainClass
+ * @return
+ */
+ protected abstract RepositorySupport getTargetRepository(
+ Class domainClass);
+
+
+ /**
+ * Create a {@link QueryMethod} instance for the given {@link Method}.
+ *
+ * @param method
+ * @return
+ */
+ protected abstract QueryMethod getQueryMethod(Method method);
+
+
+ /**
+ * Determines the base class for the repository to be created.
+ *
+ * @return
+ */
+ @SuppressWarnings("rawtypes")
+ protected abstract Class extends RepositorySupport> getRepositoryClass();
+
+
+ /**
+ * Returns the {@link QueryLookupStrategy} for the given {@link Key}.
+ *
+ * @param key can be {@literal null}
+ * @return
+ */
+ protected abstract QueryLookupStrategy getQueryLookupStrategy(Key key);
+
+
+ /**
+ * Returns if the configured DAO interface has custom methods, that might
+ * have to be delegated to a custom DAO implementation. This is used to
+ * verify DAO configuration.
+ *
+ * @return
+ */
+ private boolean hasCustomMethod(
+ Class extends Repository, ?>> daoInterface) {
+
+ boolean hasCustomMethod = false;
+
+ // No detection required if no typing interface was configured
+ if (isGenericRepositoryInterface(daoInterface)) {
+ return false;
+ }
+
+ for (Method method : daoInterface.getMethods()) {
+
+ if (isCustomMethod(method, daoInterface)
+ && !isBaseClassMethod(method, daoInterface)) {
+ return true;
+ }
+ }
+
+ return hasCustomMethod;
+ }
+
+
+ /**
+ * Returns whether the given method is considered to be a DAO base class
+ * method.
+ *
+ * @param method
+ * @return
+ */
+ private boolean isBaseClassMethod(Method method, Class> daoInterface) {
+
+ Assert.notNull(method);
+
+ if (method.getDeclaringClass().isAssignableFrom(getRepositoryClass())) {
+ return true;
+ }
+
+ return !method.equals(getBaseClassMethod(method, daoInterface));
+ }
+
+
+ /**
+ * Returns the base class method that is backing the given method. This can
+ * be necessary if a DAO interface redeclares a method in {@link GenericDao}
+ * (e.g. for transaction behaviour customization). Returns the method itself
+ * if the base class does not implement the given method.
+ *
+ * @param method
+ * @return
+ */
+ private Method getBaseClassMethod(Method method, Class> daoInterface) {
+
+ Assert.notNull(method);
+
+ Method result = methodCache.get(method);
+
+ if (null != result) {
+ return result;
+ }
+
+ result =
+ getBaseClassMethodFor(method, getRepositoryClass(),
+ daoInterface);
+ methodCache.put(method, result);
+
+ return result;
+ }
+
+
+ /**
+ * Returns whether the given method is a custom DAO method.
+ *
+ * @param method
+ * @param daoInterface
+ * @return
+ */
+ private boolean isCustomMethod(Method method, Class> daoInterface) {
+
+ Class> declaringClass = method.getDeclaringClass();
+
+ boolean isQueryMethod = declaringClass.equals(daoInterface);
+ boolean isHadesDaoInterface =
+ isGenericRepositoryInterface(declaringClass);
+ boolean isBaseClassMethod = isBaseClassMethod(method, daoInterface);
+
+ return !(isHadesDaoInterface || isBaseClassMethod || isQueryMethod);
+ }
+
+
+ /**
+ * Returns all methods considered to be finder methods.
+ *
+ * @param daoInterface
+ * @return
+ */
+ private Iterable getFinderMethods(Class> daoInterface) {
+
+ Set result = new HashSet();
+
+ for (Method method : daoInterface.getDeclaredMethods()) {
+ if (!isCustomMethod(method, daoInterface)
+ && !isBaseClassMethod(method, daoInterface)) {
+ result.add(method);
+ }
+ }
+
+ return result;
+ }
+
+
+ /**
+ * Validates the given repository interface.
+ *
+ * @param daoInterface
+ */
+ private void validate(Class> daoInterface) {
+
+ Assert.notNull(daoInterface);
+ Assert.notNull(
+ getDomainClass(daoInterface),
+ "Could not retrieve domain class from interface. Make sure it extends GenericRepository.");
+
+ }
+
+
+ /**
+ * Validates the given repository interface as well as the given custom
+ * implementation.
+ *
+ * @param repositoryInterface
+ * @param customImplementation
+ */
+ protected void validate(
+ Class extends Repository, ?>> repositoryInterface,
+ Object customImplementation) {
+
+ validate(repositoryInterface);
+
+ if (null == customImplementation
+ && hasCustomMethod(repositoryInterface)) {
+
+ throw new IllegalArgumentException(
+ String.format(
+ "You have custom methods in %s but not provided a custom implementation!",
+ repositoryInterface));
+ }
+ }
+
+ /**
+ * This {@code MethodInterceptor} intercepts calls to methods of the custom
+ * implementation and delegates the to it if configured. Furthermore it
+ * resolves method calls to finders and triggers execution of them. You can
+ * rely on having a custom repository implementation instance set if this
+ * returns true.
+ *
+ * @author Oliver Gierke
+ */
+ public class QueryExecuterMethodInterceptor implements MethodInterceptor {
+
+ private final Map queries =
+ new ConcurrentHashMap();
+
+ private final Object customImplementation;
+ private final Class> repositoryInterface;
+ private final RepositorySupport, ?> target;
+
+
+ /**
+ * Creates a new {@link QueryExecuterMethodInterceptor}. Builds a model
+ * of {@link QueryMethod}s to be invoked on execution of repository
+ * interface methods.
+ */
+ public QueryExecuterMethodInterceptor(Class> repositoryInterface,
+ Object customImplementation,
+ RepositorySupport, ?> target) {
+
+ this.repositoryInterface = repositoryInterface;
+ this.customImplementation = customImplementation;
+ this.target = target;
+
+ QueryLookupStrategy strategy =
+ getQueryLookupStrategy(queryLookupStrategyKey);
+
+ for (Method method : getFinderMethods(repositoryInterface)) {
+ QueryMethod queryMethod = getQueryMethod(method);
+ queries.put(method, strategy.resolveQuery(queryMethod));
+ }
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance
+ * .intercept.MethodInvocation)
+ */
+ public Object invoke(MethodInvocation invocation) throws Throwable {
+
+ Method method = invocation.getMethod();
+
+ if (isCustomMethodInvocation(invocation)) {
+
+ makeAccessible(method);
+ return executeMethodOn(customImplementation, method,
+ invocation.getArguments());
+ }
+
+ if (hasQueryFor(method)) {
+ return queries.get(method).execute(invocation.getArguments());
+ }
+
+ // Lookup actual method as it might be redeclared in the interface
+ // and we have to use the repository instance nevertheless
+ Method actualMethod =
+ getBaseClassMethod(method, repositoryInterface);
+ return executeMethodOn(target, actualMethod,
+ invocation.getArguments());
+ }
+
+
+ /**
+ * Executes the given method on the given target. Correctly unwraps
+ * exceptions not caused by the reflection magic.
+ *
+ * @param target
+ * @param method
+ * @param parameters
+ * @return
+ * @throws Throwable
+ */
+ private Object executeMethodOn(Object target, Method method,
+ Object[] parameters) throws Throwable {
+
+ try {
+ return method.invoke(target, parameters);
+ } catch (Exception e) {
+ ClassUtils.unwrapReflectionException(e);
+ }
+
+ throw new IllegalStateException("Should not occur!");
+ }
+
+
+ /**
+ * Returns whether we know of a query to execute for the given
+ * {@link Method};
+ *
+ * @param method
+ * @return
+ */
+ private boolean hasQueryFor(Method method) {
+
+ return queries.containsKey(method);
+ }
+
+
+ /**
+ * Returns whether the given {@link MethodInvocation} is considered to
+ * be targeted as an invocation of a custom method.
+ *
+ * @param method
+ * @return
+ */
+ private boolean isCustomMethodInvocation(MethodInvocation invocation) {
+
+ if (null == customImplementation) {
+ return false;
+ }
+
+ return isCustomMethod(invocation.getMethod(), repositoryInterface);
+ }
+ }
+}
diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/RepositoryInterfaceAwareBeanPostProcessor.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/RepositoryInterfaceAwareBeanPostProcessor.java
new file mode 100644
index 000000000..f3fb59d45
--- /dev/null
+++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/RepositoryInterfaceAwareBeanPostProcessor.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2008-2010 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.support;
+
+import org.springframework.beans.PropertyValue;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.BeanFactoryAware;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
+import org.springframework.beans.factory.config.TypedStringValue;
+import org.springframework.util.ClassUtils;
+
+
+/**
+ * A
+ * {@link org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor}
+ * implementing {@code #predictBeanType(Class, String)} to return the configured
+ * DAO interface from {@link GenericDaoFactoryBean}s. This is done as shortcut
+ * to prevent the need of instantiating {@link GenericDaoFactoryBean}s just to
+ * find out what DAO interface they actually create.
+ *
+ * @author Oliver Gierke
+ */
+class RepositoryInterfaceAwareBeanPostProcessor extends
+ InstantiationAwareBeanPostProcessorAdapter implements BeanFactoryAware {
+
+ private static final Class> REPOSITORY_TYPE =
+ RepositoryFactorySupport.class;
+
+ private ConfigurableListableBeanFactory context;
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.springframework.beans.factory.BeanFactoryAware#setBeanFactory(org
+ * .springframework.beans.factory.BeanFactory)
+ */
+ public void setBeanFactory(BeanFactory beanFactory) {
+
+ if (beanFactory instanceof ConfigurableListableBeanFactory) {
+
+ this.context = (ConfigurableListableBeanFactory) beanFactory;
+ }
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.springframework.beans.factory.config.
+ * InstantiationAwareBeanPostProcessorAdapter
+ * #predictBeanType(java.lang.Class, java.lang.String)
+ */
+ @Override
+ public Class> predictBeanType(Class> beanClass, String beanName) {
+
+ if (null == context || !REPOSITORY_TYPE.isAssignableFrom(beanClass)) {
+ return null;
+ }
+
+ BeanDefinition definition = context.getBeanDefinition(beanName);
+ PropertyValue value =
+ definition.getPropertyValues().getPropertyValue("daoInterface");
+
+ return getClassForPropertyValue(value);
+ }
+
+
+ /**
+ * Returns the class which is configured in the given {@link PropertyValue}.
+ * In case it is not a {@link TypedStringValue} or the value contained
+ * cannot be interpreted as {@link Class} it will return null.
+ *
+ * @param propertyValue
+ * @return
+ */
+ private Class> getClassForPropertyValue(PropertyValue propertyValue) {
+
+ Object value = propertyValue.getValue();
+ String className = null;
+
+ if (value instanceof TypedStringValue) {
+ className = ((TypedStringValue) value).getValue();
+ } else if (value instanceof String) {
+ className = (String) value;
+ } else if (value instanceof Class>) {
+ return (Class>) value;
+ } else {
+ return null;
+ }
+
+ try {
+ return ClassUtils.resolveClassName(className,
+ RepositoryInterfaceAwareBeanPostProcessor.class.getClassLoader());
+ } catch (IllegalArgumentException ex) {
+ return null;
+ }
+ }
+}
diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/RepositoryProxyPostProcessor.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/RepositoryProxyPostProcessor.java
new file mode 100644
index 000000000..4f931000e
--- /dev/null
+++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/RepositoryProxyPostProcessor.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2008-2010 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.support;
+
+import org.springframework.aop.framework.ProxyFactory;
+
+
+/**
+ * Callback interface used during DAO proxy creation. Allows manipulating the
+ * {@link ProxyFactory} creating the DAO.
+ *
+ * @author Oliver Gierke
+ */
+public interface RepositoryProxyPostProcessor {
+
+ /**
+ * Manipulates the {@link ProxyFactory}, e.g. add further interceptors to
+ * it.
+ *
+ * @param factory
+ */
+ void postProcess(ProxyFactory factory);
+}
\ No newline at end of file
diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/RepositorySupport.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/RepositorySupport.java
new file mode 100644
index 000000000..b52c47e25
--- /dev/null
+++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/RepositorySupport.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2008-2010 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.support;
+
+import java.io.Serializable;
+
+import org.springframework.data.domain.Persistable;
+import org.springframework.data.repository.Repository;
+import org.springframework.util.Assert;
+
+
+/**
+ * Abstract base class for generic repositories. Captures information about the
+ * domain class to be managed.
+ *
+ * @author Oliver Gierke
+ * @param the type of entity to be handled
+ */
+public abstract class RepositorySupport implements
+ Repository {
+
+ private final Class domainClass;
+ private final IsNewAware isNewStrategy;
+
+
+ /**
+ * Creates a new {@link RepositorySupport}.
+ *
+ * @param domainClass
+ */
+ public RepositorySupport(Class domainClass) {
+
+ Assert.notNull(domainClass);
+ this.domainClass = domainClass;
+ this.isNewStrategy = createIsNewStrategy(domainClass);
+ Assert.notNull(isNewStrategy);
+ }
+
+
+ /**
+ * Returns the domain class to handle.
+ *
+ * @return the domain class
+ */
+ protected Class getDomainClass() {
+
+ return domainClass;
+ }
+
+
+ /**
+ * Return whether the given entity is to be regarded as new. Default
+ * implementation will inspect the given domain class and use either
+ * {@link PersistableEntityInformation} if the class implements
+ * {@link Persistable} or {@link ReflectiveEntityInformation} otherwise.
+ *
+ * @param entity
+ * @return
+ */
+ protected abstract IsNewAware createIsNewStrategy(Class> domainClass);
+
+
+ /**
+ * Returns the strategy how to determine whether an entity is to be regarded
+ * as new.
+ *
+ * @return the isNewStrategy
+ */
+ protected IsNewAware getIsNewStrategy() {
+
+ return isNewStrategy;
+ }
+}
diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/TransactionalRepositoryProxyPostProcessor.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/TransactionalRepositoryProxyPostProcessor.java
new file mode 100644
index 000000000..36a30fe16
--- /dev/null
+++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/TransactionalRepositoryProxyPostProcessor.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2008-2010 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.support;
+
+import org.springframework.aop.framework.ProxyFactory;
+import org.springframework.beans.factory.ListableBeanFactory;
+import org.springframework.dao.support.PersistenceExceptionTranslationInterceptor;
+import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource;
+import org.springframework.transaction.interceptor.TransactionInterceptor;
+import org.springframework.util.Assert;
+
+
+/**
+ * {@link DaoProxyPostProcessor} to add transactional behaviour to DAO proxies.
+ * Adds a {@link PersistenceExceptionTranslationInterceptor} as well as an
+ * annotation based {@link TransactionInterceptor} to the proxy.
+ *
+ * @author Oliver Gierke
+ */
+class TransactionalRepositoryProxyPostProcessor implements
+ RepositoryProxyPostProcessor {
+
+ private final TransactionInterceptor transactionInterceptor;
+ private final PersistenceExceptionTranslationInterceptor petInterceptor;
+
+
+ /**
+ * Creates a new {@link TransactionalRepositoryProxyPostProcessor}.
+ */
+ public TransactionalRepositoryProxyPostProcessor(
+ ListableBeanFactory beanFactory, String transactionManagerName) {
+
+ Assert.notNull(beanFactory);
+ Assert.notNull(transactionManagerName);
+
+ this.petInterceptor = new PersistenceExceptionTranslationInterceptor();
+ this.petInterceptor.setBeanFactory(beanFactory);
+ this.petInterceptor.afterPropertiesSet();
+
+ this.transactionInterceptor =
+ new TransactionInterceptor(null,
+ new AnnotationTransactionAttributeSource());
+ this.transactionInterceptor
+ .setTransactionManagerBeanName(transactionManagerName);
+ this.transactionInterceptor.setBeanFactory(beanFactory);
+ this.transactionInterceptor.afterPropertiesSet();
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.synyx.hades.dao.orm.DaoProxyPostProcessor#postProcess(org.springframework
+ * .aop.framework.ProxyFactory)
+ */
+ public void postProcess(ProxyFactory factory) {
+
+ factory.addAdvice(petInterceptor);
+ factory.addAdvice(transactionInterceptor);
+ }
+}
diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/util/ClassUtils.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/util/ClassUtils.java
new file mode 100644
index 000000000..35e9618a9
--- /dev/null
+++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/util/ClassUtils.java
@@ -0,0 +1,356 @@
+/*
+ * Copyright 2008-2010 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.util;
+
+import static org.springframework.core.GenericTypeResolver.*;
+
+import java.io.Serializable;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.util.Arrays;
+import java.util.Collection;
+
+import org.springframework.data.repository.Repository;
+import org.springframework.util.ReflectionUtils;
+import org.springframework.util.StringUtils;
+
+
+/**
+ * Utility class to work with classes.
+ *
+ * @author Oliver Gierke
+ */
+public abstract class ClassUtils {
+
+ @SuppressWarnings("rawtypes")
+ private static final TypeVariable>[] PARAMETERS =
+ Repository.class.getTypeParameters();
+ private static final String DOMAIN_TYPE_NAME = PARAMETERS[0].getName();
+ private static final String ID_TYPE_NAME = PARAMETERS[1].getName();
+
+
+ /**
+ * Private constructor to prevent instantiation.
+ */
+ private ClassUtils() {
+
+ }
+
+
+ /**
+ * Returns the domain class the given class is declared for. Will introspect
+ * the given class for extensions of {@link Repository} and retrieve
+ * the domain class type from its generics declaration.
+ *
+ * @param clazz
+ * @return the domain class the given class is repository for or
+ * {@code null} if none found.
+ */
+ public static Class> getDomainClass(Class> clazz) {
+
+ Class>[] arguments =
+ resolveTypeArguments(clazz, Repository.class);
+ return arguments == null ? null : arguments[0];
+ }
+
+
+ /**
+ * Returns the id class the given class is declared for. Will introspect the
+ * given class for extensions of {@link Repository} or and retrieve
+ * the {@link Serializable} type from its generics declaration.
+ *
+ * @param clazz
+ * @return the id class the given class is repository for or {@code null} if
+ * none found.
+ */
+ @SuppressWarnings("unchecked")
+ public static Class extends Serializable> getIdClass(Class> clazz) {
+
+ Class>[] arguments =
+ resolveTypeArguments(clazz, Repository.class);
+ return (Class extends Serializable>) (arguments == null ? null
+ : arguments[1]);
+ }
+
+
+ /**
+ * Returns the domain class returned by the given {@link Method}. Will
+ * extract the type from {@link Collection}s and
+ * {@link org.springframework.data.domain.Page} as well.
+ *
+ * @param method
+ * @return
+ */
+ public static Class> getReturnedDomainClass(Method method) {
+
+ Type type = method.getGenericReturnType();
+
+ if (type instanceof ParameterizedType) {
+ return (Class>) ((ParameterizedType) type)
+ .getActualTypeArguments()[0];
+
+ } else {
+ return method.getReturnType();
+ }
+ }
+
+
+ /**
+ * Returns whether the given class contains a property with the given name.
+ *
+ * @param fieldName
+ * @return
+ */
+ public static boolean hasProperty(Class> type, String property) {
+
+ if (null != ReflectionUtils.findMethod(type, "get" + property)) {
+ return true;
+ }
+
+ return null != ReflectionUtils.findField(type,
+ StringUtils.uncapitalize(property));
+ }
+
+
+ /**
+ * Returns wthere the given type is the {@link Repository} interface.
+ *
+ * @param interfaze
+ * @return
+ */
+ public static boolean isGenericRepositoryInterface(Class> interfaze) {
+
+ return Repository.class.equals(interfaze);
+ }
+
+
+ /**
+ * Returns whether the given type name is a repository interface name.
+ *
+ * @param interfaceName
+ * @return
+ */
+ public static boolean isGenericRepositoryInterface(String interfaceName) {
+
+ return Repository.class.getName().equals(interfaceName);
+ }
+
+
+ /**
+ * Returns the number of occurences of the given type in the given
+ * {@link Method}s parameters.
+ *
+ * @param method
+ * @param type
+ * @return
+ */
+ public static int getNumberOfOccurences(Method method, Class> type) {
+
+ int result = 0;
+ for (Class> clazz : method.getParameterTypes()) {
+ if (type.equals(clazz)) {
+ result++;
+ }
+ }
+
+ return result;
+ }
+
+
+ /**
+ * Asserts the given {@link Method}'s return type to be one of the given
+ * types.
+ *
+ * @param method
+ * @param types
+ */
+ public static void assertReturnType(Method method, Class>... types) {
+
+ if (!Arrays.asList(types).contains(method.getReturnType())) {
+ throw new IllegalStateException(
+ "Method has to have one of the following return types! "
+ + Arrays.toString(types));
+ }
+ }
+
+
+ /**
+ * Returns whether the given object is of one of the given types. Will
+ * return {@literal false} for {@literal null}.
+ *
+ * @param object
+ * @param types
+ * @return
+ */
+ public static boolean isOfType(Object object, Collection> types) {
+
+ if (null == object) {
+ return false;
+ }
+
+ for (Class> type : types) {
+ if (type.isAssignableFrom(object.getClass())) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+
+ /**
+ * Returns whether the given {@link Method} has a parameter of the given
+ * type.
+ *
+ * @param method
+ * @param type
+ * @return
+ */
+ public static boolean hasParameterOfType(Method method, Class> type) {
+
+ return Arrays.asList(method.getParameterTypes()).contains(type);
+ }
+
+
+ /**
+ * Helper method to extract the original exception that can possibly occur
+ * during a reflection call.
+ *
+ * @param ex
+ * @throws Throwable
+ */
+ public static void unwrapReflectionException(Exception ex) throws Throwable {
+
+ if (ex instanceof InvocationTargetException) {
+ throw ((InvocationTargetException) ex).getTargetException();
+ }
+
+ throw ex;
+ }
+
+
+ /**
+ * Returns the given base class' method if the given method (declared in the
+ * interface) was also declared at the base class. Returns the given method
+ * if the given base class does not declare the method given. Takes generics
+ * into account.
+ *
+ * @param method
+ * @param baseClass
+ * @param repositoryInterface
+ * @return
+ */
+ public static Method getBaseClassMethodFor(Method method,
+ Class> baseClass, Class> repositoryInterface) {
+
+ for (Method baseClassMethod : baseClass.getDeclaredMethods()) {
+
+ // Wrong name
+ if (!method.getName().equals(baseClassMethod.getName())) {
+ continue;
+ }
+
+ // Wrong number of arguments
+ if (!(method.getParameterTypes().length == baseClassMethod
+ .getParameterTypes().length)) {
+ continue;
+ }
+
+ // Check whether all parameters match
+ if (!parametersMatch(method, baseClassMethod, repositoryInterface)) {
+ continue;
+ }
+
+ return baseClassMethod;
+ }
+
+ return method;
+ }
+
+
+ /**
+ * Checks the given method's parameters to match the ones of the given base
+ * class method. Matches generic arguments agains the ones bound in the
+ * given repository interface.
+ *
+ * @param method
+ * @param baseClassMethod
+ * @param repositoryInterface
+ * @return
+ */
+ private static boolean parametersMatch(Method method,
+ Method baseClassMethod, Class> repositoryInterface) {
+
+ Type[] genericTypes = baseClassMethod.getGenericParameterTypes();
+ Class>[] types = baseClassMethod.getParameterTypes();
+ Class>[] methodParameters = method.getParameterTypes();
+
+ for (int i = 0; i < genericTypes.length; i++) {
+
+ Type type = genericTypes[i];
+
+ if (type instanceof TypeVariable>) {
+
+ String name = ((TypeVariable>) type).getName();
+
+ if (!matchesGenericType(name, methodParameters[i],
+ repositoryInterface)) {
+ return false;
+ }
+
+ } else {
+
+ if (!types[i].equals(methodParameters[i])) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+
+ /**
+ * Checks whether the given parameter type matches the generic type of the
+ * given parameter. Thus when {@literal PK} is declared, the method ensures
+ * that given method parameter is the primary key type declared in the given
+ * repository interface e.g.
+ *
+ * @param name
+ * @param parameterType
+ * @param repositoryInterface
+ * @return
+ */
+ private static boolean matchesGenericType(String name,
+ Class> parameterType, Class> repositoryInterface) {
+
+ Class> entityType = getDomainClass(repositoryInterface);
+ Class> idClass = getIdClass(repositoryInterface);
+
+ if (ID_TYPE_NAME.equals(name) && parameterType.equals(idClass)) {
+ return true;
+ }
+
+ if (DOMAIN_TYPE_NAME.equals(name) && parameterType.equals(entityType)) {
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/util/TxUtils.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/util/TxUtils.java
new file mode 100644
index 000000000..f77c3c21b
--- /dev/null
+++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/util/TxUtils.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2008-2010 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.util;
+
+/**
+ * Simple constants holder.
+ *
+ * @author Oliver Gierke
+ */
+public abstract class TxUtils {
+
+ private TxUtils() {
+
+ }
+
+ public static final String DEFAULT_TRANSACTION_MANAGER =
+ "transactionManager";
+}
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
new file mode 100644
index 000000000..22d3f4bfa
--- /dev/null
+++ b/spring-data-commons-core/src/main/resources/META-INF/spring.schemas
@@ -0,0 +1,2 @@
+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
diff --git a/spring-data-commons-core/src/main/resources/org/springframework/data/repository/config/spring-repository-1.0.xsd b/spring-data-commons-core/src/main/resources/org/springframework/data/repository/config/spring-repository-1.0.xsd
new file mode 100644
index 000000000..cea1bab0d
--- /dev/null
+++ b/spring-data-commons-core/src/main/resources/org/springframework/data/repository/config/spring-repository-1.0.xsd
@@ -0,0 +1,139 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Declares a single DAO instance.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spring-data-commons-core/src/test/java/org/springframework/data/domain/DirectionUnitTests.java b/spring-data-commons-core/src/test/java/org/springframework/data/domain/DirectionUnitTests.java
new file mode 100644
index 000000000..8582d653a
--- /dev/null
+++ b/spring-data-commons-core/src/test/java/org/springframework/data/domain/DirectionUnitTests.java
@@ -0,0 +1,29 @@
+package org.springframework.data.domain;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+import org.springframework.data.domain.Sort.Direction;
+
+
+/**
+ * Unit test for {@link Direction}.
+ *
+ * @author Oliver Gierke
+ */
+public class DirectionUnitTests {
+
+ @Test
+ public void jpaValueMapping() throws Exception {
+
+ assertEquals(Direction.ASC, Direction.fromString("asc"));
+ assertEquals(Direction.DESC, Direction.fromString("desc"));
+ }
+
+
+ @Test(expected = IllegalArgumentException.class)
+ public void rejectsInvalidString() throws Exception {
+
+ Direction.fromString("foo");
+ }
+}
diff --git a/spring-data-commons-core/src/test/java/org/springframework/data/domain/PageImplUnitTests.java b/spring-data-commons-core/src/test/java/org/springframework/data/domain/PageImplUnitTests.java
new file mode 100644
index 000000000..ce51a8bf8
--- /dev/null
+++ b/spring-data-commons-core/src/test/java/org/springframework/data/domain/PageImplUnitTests.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2008-2010 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.domain;
+
+import static org.springframework.data.domain.UnitTestUtils.*;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.Test;
+
+
+/**
+ * Unit test for {@link PageImpl}.
+ *
+ * @author Oliver Gierke
+ */
+public class PageImplUnitTests {
+
+ @Test
+ public void assertEqualsForSimpleSetup() throws Exception {
+
+ PageImpl page = new PageImpl(Arrays.asList("Foo"));
+
+ assertEqualsAndHashcode(page, page);
+ assertEqualsAndHashcode(page,
+ new PageImpl(Arrays.asList("Foo")));
+ }
+
+
+ @Test
+ public void assertEqualsForComplexSetup() throws Exception {
+
+ Pageable pageable = new PageRequest(0, 10);
+ List content = Arrays.asList("Foo");
+
+ PageImpl page = new PageImpl(content, pageable, 100);
+
+ assertEqualsAndHashcode(page, page);
+
+ assertEqualsAndHashcode(page, new PageImpl(content, pageable,
+ 100));
+
+ assertNotEqualsAndHashcode(page, new PageImpl(content,
+ pageable, 90));
+
+ assertNotEqualsAndHashcode(page, new PageImpl(content,
+ new PageRequest(1, 10), 100));
+
+ assertNotEqualsAndHashcode(page, new PageImpl(content,
+ new PageRequest(0, 15), 100));
+ }
+
+
+ @Test(expected = IllegalArgumentException.class)
+ public void preventsNullContentForSimpleSetup() throws Exception {
+
+ new PageImpl