Initial commit for repository abstraction.

Extracted common functionality from project Hades to build a common infrastructure for generic repository implementations independent of the underlying persistence mechanism. Fixes DATACMNS-2, DATACMNS-3, DATACMNS-4, DATACMNS-5, DATACMNS-6, DATACMNS-8, DATACMNS-9.
This commit is contained in:
Oliver Gierke
2010-11-07 18:39:16 +01:00
parent 1da491ff6f
commit 6194acd825
61 changed files with 7161 additions and 1 deletions

View File

@@ -77,6 +77,13 @@
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>1.6</version>
<optional>true</optional>
</dependency>
</dependencies>
<build>

View File

@@ -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 <U> the auditing type. Typically some kind of user.
* @param <PK> the type of the auditing type's idenifier
*/
public interface Auditable<U, PK extends Serializable> extends Persistable<PK> {
/**
* 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);
}

View File

@@ -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 <T> the type of the auditing instance
*/
public interface AuditorAware<T> {
/**
* Returns the current auditor of the application.
*
* @return the current auditor
*/
T getCurrentAuditor();
}

View File

@@ -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 <T>
*/
public interface Page<T> extends Iterable<T> {
/**
* 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<T> iterator();
/**
* Returns the page content as {@link List}.
*
* @return
*/
List<T> getContent();
/**
* Returns the sorting parameters for the page.
*
* @return
*/
Sort getSort();
}

View File

@@ -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 <T> the type of which the page consists.
*/
public class PageImpl<T> implements Page<T> {
private final List<T> content = new ArrayList<T>();
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<T> 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<T> 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<T> iterator() {
return content.iterator();
}
/*
* (non-Javadoc)
*
* @see org.springframework.data.domain.Page#asList()
*/
public List<T> 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;
}
}

View File

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

View File

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

View File

@@ -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 <PK> the type of the identifier
*/
public interface Persistable<PK extends Serializable> 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();
}

View File

@@ -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<org.springframework.data.domain.Sort.Order> {
public static final Direction DEFAULT_DIRECTION = Direction.ASC;
private List<Order> 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<Order> 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<String>() : Arrays
.asList(properties));
}
/**
* Creates a new {@link Sort} instance.
*
* @param direction
* @param properties
*/
public Sort(Direction direction, List<String> properties) {
if (properties == null || properties.isEmpty()) {
throw new IllegalArgumentException(
"You have to provide at least one property to sort by!");
}
this.orders = new ArrayList<Order>(properties.size());
for (String property : properties) {
this.orders.add(new Order(direction, property));
}
}
/*
* (non-Javadoc)
*
* @see java.lang.Iterable#iterator()
*/
public Iterator<Order> 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<Order> create(Direction direction,
Iterable<String> properties) {
List<Order> orders = new ArrayList<Sort.Order>();
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);
}
}
}

View File

@@ -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.
* <p>
* 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 {
}

View File

@@ -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<T, ID extends Serializable> extends
Repository<T, ID> {
/**
* Returns all entities sorted by the given options.
*
* @param sort
* @return all entities sorted by the given options
*/
List<T> 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<T> findAll(Pageable pageable);
}

View File

@@ -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<T, ID extends Serializable> {
/**
* 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<T> 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<T> 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();
}

View File

@@ -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<S extends GlobalRepositoryConfigInformation<T>, T extends SingleRepositoryConfigInformation<S>>
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<String> repositoryInterfaces =
getRepositoryInterfacesForAutoConfig(config, resourceLoader,
parser.getReaderContext());
for (String daoInterface : repositoryInterfaces) {
registerGenericRepositoryFactoryBean(parser,
config.getAutoconfigRepositoryInformation(daoInterface));
}
}
private Set<String> 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<BeanDefinition> findCandidateComponents =
scanner.findCandidateComponents(config.getBasePackage());
Set<String> interfaceNames = new HashSet<String>();
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<BeanDefinition> 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);
}
}
}
}

View File

@@ -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<S extends CommonRepositoryConfigInformation>
extends ParentDelegatingRepositoryConfigInformation<S> {
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;
}
}

View File

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

View File

@@ -0,0 +1,43 @@
package org.springframework.data.repository.config;
/**
* @author Oliver Gierke
*/
public interface GlobalRepositoryConfigInformation<T extends SingleRepositoryConfigInformation<?>>
extends CommonRepositoryConfigInformation {
/**
* Returns the
*
* @param interfaceName
* @return
*/
T getAutoconfigRepositoryInformation(String interfaceName);
/**
* Returns all {@link SingleRepositoryConfigInformation} instances used for
* manual configuration.
*
* @return
*/
Iterable<T> 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();
}

View File

@@ -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<T extends CommonRepositoryConfigInformation>
extends ParentDelegatingRepositoryConfigInformation<T> {
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));
}
}

View File

@@ -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<T extends CommonRepositoryConfigInformation>
implements SingleRepositoryConfigInformation<T> {
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();
}
}

View File

@@ -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<T extends SingleRepositoryConfigInformation<S>, S extends CommonRepositoryConfigInformation>
implements GlobalRepositoryConfigInformation<T> {
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<T> getSingleRepositoryConfigInformations() {
Set<T> infos = new HashSet<T>();
for (Element element : getRepositoryNodes()) {
infos.add(createSingleRepositoryConfigInformationFor(element));
}
return infos;
}
private Collection<Element> getRepositoryNodes() {
NodeList nodes = element.getChildNodes();
Set<Element> result = new HashSet<Element>();
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);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<Parameter> {
@SuppressWarnings("unchecked")
public static final List<Class<?>> 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<Parameter> 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<Parameter>();
List<Class<?>> 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<Parameter> originals) {
this.parameters = new ArrayList<Parameter>();
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<Parameter> bindables = new ArrayList<Parameter>();
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<Parameter> iterator() {
return parameters.iterator();
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<Order> orders;
public OrderBySource(String clause) {
this.orders = new ArrayList<Sort.Order>();
List<String> properties = new ArrayList<String>();
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);
}
}

View File

@@ -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<Part.Type> ALL = Arrays.asList(IS_NOT_NULL,
IS_NULL, BETWEEN, LESS_THAN, GREATER_THAN, NOT_LIKE, LIKE,
NEGATING_SIMPLE_PROPERTY, SIMPLE_PROPERTY);
private List<String> 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;
}
}
}

View File

@@ -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<PartSource> getParts(String keyword) {
List<PartSource> parts = new ArrayList<PartSource>();
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);
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 <T> the type of the repository
*/
public abstract class RepositoryFactoryBeanSupport<T extends Repository<?, ?>>
implements FactoryBean<T>, 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<T> 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);
}
}

View File

@@ -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<Method, Method> methodCache =
new ConcurrentHashMap<Method, Method>();
private final List<RepositoryProxyPostProcessor> postProcessors =
new ArrayList<RepositoryProxyPostProcessor>();
/**
* 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 <em>after</em> 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 <T>
* @param repositoryInterface
* @return
*/
public <T extends Repository<?, ?>> T getRepository(
Class<T> repositoryInterface) {
return getRepository(repositoryInterface, null);
}
/**
* Returns a repository instance for the given interface backed by an
* instance providing implementation logic for custom logic.
*
* @param <T>
* @param repositoryInterface
* @param customDaoImplementation
* @return
*/
@SuppressWarnings("unchecked")
public <T extends Repository<?, ?>> T getRepository(
Class<T> 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 <T>
* @param domainClass
* @return
*/
protected abstract <T, ID extends Serializable> RepositorySupport<T, ID> getTargetRepository(
Class<T> 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<Method> getFinderMethods(Class<?> daoInterface) {
Set<Method> result = new HashSet<Method>();
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<Method, RepositoryQuery> queries =
new ConcurrentHashMap<Method, RepositoryQuery>();
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);
}
}
}

View File

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

View File

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

View File

@@ -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 <T> the type of entity to be handled
*/
public abstract class RepositorySupport<T, ID extends Serializable> implements
Repository<T, ID> {
private final Class<T> domainClass;
private final IsNewAware isNewStrategy;
/**
* Creates a new {@link RepositorySupport}.
*
* @param domainClass
*/
public RepositorySupport(Class<T> 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<T> 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;
}
}

View File

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

View File

@@ -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<Class<Repository>>[] 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<Class<?>> 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;
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,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");
}
}

View File

@@ -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<String> page = new PageImpl<String>(Arrays.asList("Foo"));
assertEqualsAndHashcode(page, page);
assertEqualsAndHashcode(page,
new PageImpl<String>(Arrays.asList("Foo")));
}
@Test
public void assertEqualsForComplexSetup() throws Exception {
Pageable pageable = new PageRequest(0, 10);
List<String> content = Arrays.asList("Foo");
PageImpl<String> page = new PageImpl<String>(content, pageable, 100);
assertEqualsAndHashcode(page, page);
assertEqualsAndHashcode(page, new PageImpl<String>(content, pageable,
100));
assertNotEqualsAndHashcode(page, new PageImpl<String>(content,
pageable, 90));
assertNotEqualsAndHashcode(page, new PageImpl<String>(content,
new PageRequest(1, 10), 100));
assertNotEqualsAndHashcode(page, new PageImpl<String>(content,
new PageRequest(0, 15), 100));
}
@Test(expected = IllegalArgumentException.class)
public void preventsNullContentForSimpleSetup() throws Exception {
new PageImpl<Object>(null);
}
@Test(expected = IllegalArgumentException.class)
public void preventsNullContentForAdvancedSetup() throws Exception {
new PageImpl<Object>(null, null, 0);
}
}

View File

@@ -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.domain;
import static org.springframework.data.domain.UnitTestUtils.*;
import org.junit.Test;
import org.springframework.data.domain.Sort.Direction;
/**
* Unit test for {@link PageRequest}.
*
* @author Oliver Gierke
*/
public class PageRequestUnitTests {
@Test(expected = IllegalArgumentException.class)
public void preventsNegativePage() {
new PageRequest(-1, 10);
}
@Test(expected = IllegalArgumentException.class)
public void preventsNegativeSize() {
new PageRequest(0, -1);
}
@Test
public void equalsRegardsSortCorrectly() {
Sort sort = new Sort(Direction.DESC, "foo");
PageRequest request = new PageRequest(0, 10, sort);
// Equals itself
assertEqualsAndHashcode(request, request);
// Equals another instance with same setup
assertEqualsAndHashcode(request, new PageRequest(0, 10, sort));
// Equals without sort entirely
assertEqualsAndHashcode(new PageRequest(0, 10), new PageRequest(0, 10));
// Is not equal to instance without sort
assertNotEqualsAndHashcode(request, new PageRequest(0, 10));
// Is not equal to instance with another sort
assertNotEqualsAndHashcode(request, new PageRequest(0, 10,
Direction.ASC, "foo"));
}
@Test
public void equalsHonoursPageAndSize() {
PageRequest request = new PageRequest(0, 10);
// Equals itself
assertEqualsAndHashcode(request, request);
// Equals same setup
assertEqualsAndHashcode(request, new PageRequest(0, 10));
// Does not equal on different page
assertNotEqualsAndHashcode(request, new PageRequest(1, 10));
// Does not equal on different size
assertNotEqualsAndHashcode(request, new PageRequest(0, 11));
}
}

View File

@@ -0,0 +1,95 @@
/*
* 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.junit.Assert.*;
import org.junit.Test;
import org.springframework.data.domain.Sort.Direction;
/**
* Unit test for {@link Sort}.
*
* @author Oliver Gierke
*/
public class SortUnitTests {
/**
* Asserts that the class applies the default sort order if no order or
* {@code null} was provided.
*
* @throws Exception
*/
@Test
public void appliesDefaultForOrder() throws Exception {
assertEquals(Sort.DEFAULT_DIRECTION, new Sort("foo").iterator().next()
.getDirection());
assertEquals(Sort.DEFAULT_DIRECTION, new Sort((Direction) null, "foo")
.iterator().next().getDirection());
}
/**
* Asserts that the class rejects {@code null} as properties array.
*
* @throws Exception
*/
@Test(expected = IllegalArgumentException.class)
public void preventsNullProperties() throws Exception {
new Sort(Direction.ASC, (String[]) null);
}
/**
* Asserts that the class rejects {@code null} values in the properties
* array.
*
* @throws Exception
*/
@Test(expected = IllegalArgumentException.class)
public void preventsNullProperty() throws Exception {
new Sort(Direction.ASC, (String) null);
}
/**
* Asserts that the class rejects empty strings in the properties array.
*
* @throws Exception
*/
@Test(expected = IllegalArgumentException.class)
public void preventsEmptyProperty() throws Exception {
new Sort(Direction.ASC, "");
}
/**
* Asserts that the class rejects no properties given at all.
*
* @throws Exception
*/
@Test(expected = IllegalArgumentException.class)
public void preventsNoProperties() throws Exception {
new Sort(Direction.ASC);
}
}

View File

@@ -0,0 +1,44 @@
package org.springframework.data.domain;
import static org.junit.Assert.*;
/**
* @author Oliver Gierke
*/
public abstract class UnitTestUtils {
private UnitTestUtils() {
}
/**
* Asserts that delivered objects both equal each other as well as return
* the same hash code.
*
* @param first
* @param second
*/
public static void assertEqualsAndHashcode(Object first, Object second) {
assertEquals(first, second);
assertEquals(second, first);
assertEquals(first.hashCode(), second.hashCode());
}
/**
* Asserts that both objects are not equal to each other and differ in hash
* code, too.
*
* @param first
* @param second
*/
public static void assertNotEqualsAndHashcode(Object first, Object second) {
assertFalse(first.equals(second));
assertFalse(second.equals(first));
assertFalse(first.hashCode() == second.hashCode());
}
}

View File

@@ -0,0 +1,108 @@
/*
* 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.mockito.Matchers.*;
import static org.mockito.Mockito.*;
import java.io.IOException;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.beans.factory.parsing.ReaderContext;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;
/**
* Unit test for {@link TypeFilterParser}.
*
* @author Oliver Gierke
*/
@RunWith(MockitoJUnitRunner.class)
public class TypeFilterParserUnitTests {
private TypeFilterParser parser;
private Element documentElement;
@Mock
private ClassLoader classLoader;
@Mock
private ReaderContext context;
@Mock
private ClassPathScanningCandidateComponentProvider scanner;
@Before
public void setUp() throws SAXException, IOException,
ParserConfigurationException {
parser = new TypeFilterParser(classLoader, context);
Resource sampleXmlFile =
new ClassPathResource("type-filter-test.xml",
TypeFilterParserUnitTests.class);
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
documentElement =
factory.newDocumentBuilder()
.parse(sampleXmlFile.getInputStream())
.getDocumentElement();
}
@Test
public void parsesIncludesCorrectly() throws Exception {
Element element =
DomUtils.getChildElementByTagName(documentElement,
"firstSample");
parser.parseFilters(element, scanner);
verify(scanner, atLeastOnce()).addIncludeFilter(
isA(AssignableTypeFilter.class));
}
@Test
public void parsesExcludesCorrectly() throws Exception {
Element element =
DomUtils.getChildElementByTagName(documentElement,
"secondSample");
parser.parseFilters(element, scanner);
verify(scanner, atLeastOnce()).addExcludeFilter(
isA(AssignableTypeFilter.class));
}
}

View File

@@ -0,0 +1,167 @@
/*
* 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.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import java.lang.reflect.Method;
import org.junit.Before;
import org.junit.Test;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
/**
* Unit test for {@link Parameters}.
*
* @author Oliver Gierke
*/
public class ParametersUnitTests {
private Method valid;
@Before
public void setUp() throws SecurityException, NoSuchMethodException {
valid = SampleDao.class.getMethod("valid", String.class);
}
@Test
public void checksValidMethodCorrectly() throws Exception {
Method validWithPageable =
SampleDao.class.getMethod("validWithPageable", String.class,
Pageable.class);
Method validWithSort =
SampleDao.class.getMethod("validWithSort", String.class,
Sort.class);
new Parameters(valid);
new Parameters(validWithPageable);
new Parameters(validWithSort);
}
@Test(expected = IllegalArgumentException.class)
public void rejectsInvalidMethodWithParamMissing() throws Exception {
Method method =
SampleDao.class.getMethod("invalidParamMissing", String.class,
String.class);
new Parameters(method);
}
@Test(expected = IllegalArgumentException.class)
public void rejectsNullMethod() throws Exception {
new Parameters(null);
}
@Test
public void detectsNamedParameterCorrectly() throws Exception {
Parameters parameters =
getParametersFor("validWithSort", String.class, Sort.class);
Parameter parameter = parameters.getParameter(0);
assertThat(parameter.isNamedParameter(), is(true));
assertThat(parameter.getPlaceholder(), is(":username"));
parameter = parameters.getParameter(1);
assertThat(parameter.isNamedParameter(), is(false));
assertThat(parameter.isSpecialParameter(), is(true));
}
@Test
public void calculatesPlaceholderPositionCorrectly() throws Exception {
Method method =
SampleDao.class.getMethod("validWithSortFirst", Sort.class,
String.class);
Parameters parameters = new Parameters(method);
assertThat(parameters.getParameter(0).getParameterPosition(), is(0));
assertThat(parameters.getParameter(1).getParameterPosition(), is(1));
method =
SampleDao.class.getMethod("validWithSortInBetween",
String.class, Sort.class, String.class);
parameters = new Parameters(method);
assertThat(parameters.getParameter(0).getParameterPosition(), is(1));
assertThat(parameters.getParameter(1).getParameterPosition(), is(0));
assertThat(parameters.getParameter(2).getParameterPosition(), is(2));
}
@Test
public void detectsEmptyParameterListCorrectly() throws Exception {
Parameters parameters = getParametersFor("emptyParameters");
assertThat(parameters.hasParameterAt(0), is(false));
}
private Parameters getParametersFor(String methodName,
Class<?>... parameterTypes) throws SecurityException,
NoSuchMethodException {
Method method = SampleDao.class.getMethod(methodName, parameterTypes);
return new Parameters(method);
}
static class User {
}
static interface SampleDao {
User valid(@Param("username") String username);
User invalidParamMissing(@Param("username") String username,
String lastname);
User validWithPageable(@Param("username") String username,
Pageable pageable);
User validWithSort(@Param("username") String username, Sort sort);
User validWithSortFirst(Sort sort, String username);
User validWithSortInBetween(String firstname, Sort sort, String lastname);
User emptyParameters();
}
}

View File

@@ -0,0 +1,66 @@
/*
* 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.parser;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.springframework.data.domain.Sort.Direction.*;
import org.junit.Test;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Order;
/**
* Unit test for {@link OrderBySource}.
*
* @author Oliver Gierke
*/
public class OrderBySourceUnitTests {
@Test
public void handlesSingleDirectionAndPropertyCorrectly() throws Exception {
assertThat(new OrderBySource("UsernameDesc").toSort(), is(new Sort(
DESC, "username")));
}
@Test
public void handlesSingleDirectionAndMultiplePropertiesCorrectly()
throws Exception {
assertThat(new OrderBySource("LastnameUsernameDesc").toSort(),
is(new Sort(DESC, "lastname", "username")));
}
@Test
public void handlesMultipleDirectionsCorrectly() throws Exception {
OrderBySource orderBySource =
new OrderBySource("LastnameAscUsernameDesc");
assertThat(orderBySource.toSort(), is(new Sort(new Order(ASC,
"lastname"), new Order(DESC, "username"))));
}
@Test(expected = IllegalArgumentException.class)
public void rejectsMissingProperty() throws Exception {
new OrderBySource("Desc");
}
}

View File

@@ -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.repository.support;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import org.junit.Test;
import org.springframework.data.domain.Persistable;
/**
* Unit test for {@link PersistableEntityInformation}.
*
* @author Oliver Gierke
*/
public class PersistableEntityInformationTests {
@Test
public void detectsPersistableCorrectly() throws Exception {
PersistableEntityInformation info = new PersistableEntityInformation();
assertNewAndNoId(info, new PersistableEntity(null));
assertNotNewAndId(info, new PersistableEntity(1L), 1L);
}
private <T extends IdAware & IsNewAware> void assertNewAndNoId(T info,
Object entity) {
assertThat(info.isNew(entity), is(true));
assertThat(info.getId(entity), is(nullValue()));
}
private <T extends IdAware & IsNewAware> void assertNotNewAndId(T info,
Object entity, Object id) {
assertThat(info.isNew(entity), is(false));
assertThat(info.getId(entity), is(id));
}
static class PersistableEntity implements Persistable<Long> {
private static final long serialVersionUID = -5898780128204716452L;
private final Long id;
public PersistableEntity(Long id) {
this.id = id;
}
public Long getId() {
return id;
}
public boolean isNew() {
return id == null;
}
}
}

View File

@@ -0,0 +1,126 @@
/*
* 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.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.data.repository.Repository;
/**
* Unit tests for {@link RepositoryInterfaceAwareBeanPostProcessor}.
*
* @author Oliver Gierke
*/
@RunWith(MockitoJUnitRunner.class)
public class RepositoryInterfaceAwareBeanPostProcessorUnitTests {
private static final Class<?> FACTORY_CLASS =
RepositoryFactorySupport.class;
private static final String BEAN_NAME = "foo";
private static final String DAO_INTERFACE_PROPERTY = "daoInterface";
private RepositoryInterfaceAwareBeanPostProcessor processor;
@Mock
private ConfigurableListableBeanFactory beanFactory;
private BeanDefinition beanDefinition;
@Before
public void setUp() {
BeanDefinitionBuilder builder =
BeanDefinitionBuilder
.rootBeanDefinition(FACTORY_CLASS)
.addPropertyValue(DAO_INTERFACE_PROPERTY, UserDao.class);
this.beanDefinition = builder.getBeanDefinition();
when(beanFactory.getBeanDefinition(BEAN_NAME)).thenReturn(
beanDefinition);
processor = new RepositoryInterfaceAwareBeanPostProcessor();
}
@Test
public void returnsDaoInterfaceClassForFactoryBean() throws Exception {
processor.setBeanFactory(beanFactory);
assertEquals(UserDao.class,
processor.predictBeanType(FACTORY_CLASS, BEAN_NAME));
}
@Test
public void doesNotResolveInterfaceForNonFactoryClasses() throws Exception {
processor.setBeanFactory(beanFactory);
assertNotTypeDetected(BeanFactory.class);
}
@Test
public void doesNotResolveInterfaceForUnloadableClass() throws Exception {
BeanDefinitionBuilder builder =
BeanDefinitionBuilder.rootBeanDefinition(FACTORY_CLASS)
.addPropertyValue(DAO_INTERFACE_PROPERTY,
"com.acme.Foo");
when(beanFactory.getBeanDefinition(BEAN_NAME)).thenReturn(
builder.getBeanDefinition());
assertNotTypeDetected(FACTORY_CLASS);
}
@Test
public void doesNotResolveTypeForPlainBeanFactory() throws Exception {
BeanFactory beanFactory = mock(BeanFactory.class);
processor.setBeanFactory(beanFactory);
assertNotTypeDetected(FACTORY_CLASS);
}
private void assertNotTypeDetected(Class<?> beanClass) {
assertThat(processor.predictBeanType(beanClass, BEAN_NAME),
is(nullValue()));
}
private class User {
}
private interface UserDao extends Repository<User, Long> {
}
}

View File

@@ -0,0 +1,91 @@
/*
* 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.mockito.Matchers.*;
import static org.mockito.Mockito.*;
import java.util.HashMap;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.dao.support.PersistenceExceptionTranslationInterceptor;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.transaction.interceptor.TransactionInterceptor;
/**
* Unit test for {@link TransactionalRepositoryProxyPostProcessor}.
*
* @author Oliver Gierke
*/
@RunWith(MockitoJUnitRunner.class)
public class TransactionRepositoryProxyPostProcessorUnitTests {
TransactionalRepositoryProxyPostProcessor processor;
@Mock
ListableBeanFactory beanFactory;
@Mock
ProxyFactory proxyFactory;
@Before
public void setUp() {
Map<String, PersistenceExceptionTranslator> beans =
new HashMap<String, PersistenceExceptionTranslator>();
beans.put("foo", mock(PersistenceExceptionTranslator.class));
when(
beanFactory.getBeansOfType(
eq(PersistenceExceptionTranslator.class), anyBoolean(),
anyBoolean())).thenReturn(beans);
}
@Test(expected = IllegalArgumentException.class)
public void rejectsNullBeanFactory() throws Exception {
new TransactionalRepositoryProxyPostProcessor(null, "transactionManager");
}
@Test(expected = IllegalArgumentException.class)
public void rejectsNullTxManagerName() throws Exception {
new TransactionalRepositoryProxyPostProcessor(beanFactory, null);
}
@Test
public void setsUpBasicInstance() throws Exception {
RepositoryProxyPostProcessor postProcessor =
new TransactionalRepositoryProxyPostProcessor(beanFactory, "txManager");
postProcessor.postProcess(proxyFactory);
verify(proxyFactory).addAdvice(
isA(PersistenceExceptionTranslationInterceptor.class));
verify(proxyFactory).addAdvice(isA(TransactionInterceptor.class));
}
}

View File

@@ -0,0 +1,211 @@
/*
* 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.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.springframework.data.repository.util.ClassUtils.*;
import java.io.Serializable;
import java.lang.reflect.Method;
import org.junit.Test;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.support.RepositorySupport;
/**
* Unit test for {@link ClassUtils}.
*
* @author Oliver Gierke
*/
public class ClassUtilsUnitTests {
@Test
public void looksUpDomainClassCorrectly() throws Exception {
assertEquals(User.class, getDomainClass(UserRepository.class));
assertEquals(User.class, getDomainClass(SomeDao.class));
assertNull(getDomainClass(Serializable.class));
}
@Test
public void looksUpIdClassCorrectly() throws Exception {
assertEquals(Integer.class, getIdClass(UserRepository.class));
assertNull(getIdClass(Serializable.class));
}
@Test(expected = IllegalStateException.class)
public void rejectsInvalidReturnType() throws Exception {
assertReturnType(SomeDao.class.getMethod("findByFirstname",
Pageable.class, String.class), User.class);
}
@Test
public void findsDomainClassOnExtensionOfDaoInterface() throws Exception {
assertEquals(User.class,
getDomainClass(ExtensionOfUserCustomExtendedDao.class));
}
@Test
public void determinesValidFieldsCorrectly() {
assertTrue(hasProperty(User.class, "firstname"));
assertTrue(hasProperty(User.class, "Firstname"));
assertFalse(hasProperty(User.class, "address"));
}
/**
* References #256.
*/
@Test
public void detectsParameterizedEntitiesCorrectly() {
assertEquals(GenericEntity.class,
getDomainClass(GenericEntityDao.class));
}
/**
* #301
*/
@Test
public void discoversDaoBaseClassMethod() throws Exception {
Method method = FooDao.class.getMethod("findById", Integer.class);
Method reference =
getBaseClassMethodFor(method,
DummyGenericRepositorySupport.class, FooDao.class);
assertEquals(DummyGenericRepositorySupport.class,
reference.getDeclaringClass());
assertThat(reference.getName(), is("findById"));
}
/**
* #301
*/
@Test
public void discoveresNonDaoBaseClassMethod() throws Exception {
Method method = FooDao.class.getMethod("readById", Long.class);
assertThat(
getBaseClassMethodFor(method, RepositorySupport.class,
FooDao.class), is(method));
}
@SuppressWarnings("unused")
private class User {
private String firstname;
public String getAddress() {
return null;
}
}
static interface UserRepository extends Repository<User, Integer> {
}
/**
* Sample interface to serve two purposes:
* <ol>
* <li>Check that {@link ClassUtils#getDomainClass(Class)} skips non
* {@link GenericDao} interfaces</li>
* <li>Check that {@link ClassUtils#getDomainClass(Class)} traverses
* interface hierarchy</li>
* </ol>
*
* @author Oliver Gierke
*/
private interface SomeDao extends Serializable, UserRepository {
Page<User> findByFirstname(Pageable pageable, String firstname);
}
/**
* Sample interface to test recursive lookup of domain class.
*
* @author Oliver Gierke
*/
static interface ExtensionOfUserCustomExtendedDao extends
UserCustomExtendedRepository {
}
static interface UserCustomExtendedRepository extends
Repository<User, Integer> {
}
/**
* Helper class to reproduce #256.
*
* @author Oliver Gierke
*/
static class GenericEntity<T> {
}
static interface GenericEntityDao extends
Repository<GenericEntity<String>, Long> {
}
/**
* Sample DAO interface to test redeclaration of {@link GenericDao} methods.
*
* @author Oliver Gierke
*/
private static interface FooDao extends Repository<User, Integer> {
// Redeclared method
User findById(Integer primaryKey);
// Not a redeclared method
User readById(Long primaryKey);
}
static abstract class DummyGenericRepositorySupport<T, ID extends Serializable>
extends RepositorySupport<T, ID> {
public DummyGenericRepositorySupport(Class<T> domainClass) {
super(domainClass);
}
public T findById(ID id) {
return null;
}
}
}

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<tests xmlns="foo">
<firstSample>
<include-filter type="assignable" expression="java.util.Collection" />
</firstSample>
<secondSample>
<exclude-filter type="assignable" expression="java.util.Collection" />
</secondSample>
</tests>

View File

@@ -4,9 +4,11 @@ Bundle-Vendor: SpringSource
Bundle-ManifestVersion: 2
Import-Package:
sun.reflect;version="0";resolution:=optional
Import-Template:
Import-Template:
org.springframework.aop.*;version="[3.0.0, 4.0.0)",
org.springframework.beans.*;version="[3.0.0, 4.0.0)",
org.springframework.core.*;version="[3.0.0, 4.0.0)",
org.springframework.context.*;version="[3.0.0, 4.0.0)",
org.springframework.dao.*;version="[3.0.0, 4.0.0)",
org.springframework.util.*;version="[3.0.0, 4.0.0)",
org.springframework.transaction.*;version="[3.0.0, 4.0.0)",
@@ -14,6 +16,8 @@ Import-Template:
org.springframework.data.persistence.*;version="[1.0.0, 2.0.0)",
org.aopalliance.*;version="[1.0.0, 2.0.0)";resolution:=optional,
org.apache.commons.logging.*;version="[1.1.1, 2.0.0)",
org.joda.time.*;version="[1.6.0,2.0.0)",
org.slf4j.*;version="[1.5.0,1.6.0)",
org.w3c.dom.*;version="0"

View File

@@ -205,6 +205,13 @@
<version>${log4j.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
<version>1.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>