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:
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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> {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user