org.apache.cayenne.remote.RemoteIncrementalFaultList Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cayenne-client-nodeps
Show all versions of cayenne-client-nodeps
Cayenne Object Persistence Framework
/*****************************************************************
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.cayenne.remote;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.NoSuchElementException;
import org.apache.cayenne.CayenneRuntimeException;
import org.apache.cayenne.ObjectContext;
import org.apache.cayenne.Persistent;
import org.apache.cayenne.QueryResponse;
import org.apache.cayenne.query.Query;
import org.apache.cayenne.query.QueryMetadata;
import org.apache.cayenne.util.IDUtil;
import org.apache.cayenne.util.IncrementalListResponse;
import org.apache.cayenne.util.Util;
/**
* A list that serves as a container of Persistent objects. It is usually returned by an
* ObjectContext when a paginated query is performed. Initially only the first "page" of
* objects is fully resolved. Pages following the first page are resolved on demand. When
* a list element is accessed, the list would ensure that this element as well as all its
* siblings on the same page are fully resolved.
*
* The list can hold DataRows or Persistent objects. Attempts to add any other object
* types will result in an exception.
*
*
* Certain operations like toArray
would trigger full list fetch.
*
*
* Synchronization Note: this list is not synchronized. All access to it should follow
* synchronization rules applicable for ArrayList.
*
*
* @since 1.2
* @author Andrus Adamchik
*/
public class RemoteIncrementalFaultList implements List {
static final Object PLACEHOLDER = new Object();
protected List elements;
protected String cacheKey;
protected int pageSize;
protected int unfetchedObjects;
protected QueryMetadata metadata;
protected transient ObjectContext context;
/**
* Stores a hint allowing to distinguish data rows from unfetched ids when the query
* fetches data rows.
*/
protected int rowWidth;
private ListHelper helper;
public RemoteIncrementalFaultList(ObjectContext context, Query paginatedQuery) {
this.metadata = paginatedQuery.getMetaData(context.getEntityResolver());
if (metadata.getPageSize() <= 0) {
throw new IllegalArgumentException("Page size must be positive: "
+ metadata.getPageSize());
}
this.pageSize = metadata.getPageSize();
this.helper = (metadata.isFetchingDataRows())
? (ListHelper) new DataRowListHelper()
: new PersistentListHelper();
this.context = context;
// use provided cache key if possible; this would allow clients to
// address the same server-side list from multiple queries.
this.cacheKey = metadata.getCacheKey();
if(cacheKey == null) {
cacheKey = generateCacheKey();
}
IncrementalQuery query = new IncrementalQuery(paginatedQuery, cacheKey);
// select directly from the channel, bypassing the context. Otherwise our query
// wrapper can be intercepted incorrectly
QueryResponse response = context.getChannel().onQuery(context, query);
List firstPage = response.firstList();
// sanity check
if (firstPage.size() > pageSize) {
throw new IllegalArgumentException("Returned page size ("
+ firstPage.size()
+ ") exceeds requested page size ("
+ pageSize
+ ")");
}
// result is smaller than a page
else if (firstPage.size() < pageSize) {
this.elements = new ArrayList(firstPage);
unfetchedObjects = 0;
}
else {
if (response instanceof IncrementalListResponse) {
int fullListSize = ((IncrementalListResponse) response).getFullSize();
this.unfetchedObjects = fullListSize - firstPage.size();
this.elements = new ArrayList(fullListSize);
elements.addAll(firstPage);
// fill the rest with placeholder...
for (int i = pageSize; i < fullListSize; i++) {
elements.add(PLACEHOLDER);
}
}
// this happens when full size equals page size
else {
this.elements = new ArrayList(firstPage);
unfetchedObjects = 0;
}
}
}
private String generateCacheKey() {
byte[] bytes = IDUtil.pseudoUniqueByteSequence8();
StringBuffer buffer = new StringBuffer(17);
buffer.append("I");
for (int i = 0; i < bytes.length; i++) {
IDUtil.appendFormattedByte(buffer, bytes[i]);
}
return buffer.toString();
}
/**
* Will resolve all unread objects.
*/
public void resolveAll() {
resolveInterval(0, size());
}
/**
* @param object
* @return true
if the object corresponds to an unresolved state and
* does require a fetch before being returned to the user.
*/
private boolean isUnresolved(Object object) {
return object == PLACEHOLDER;
}
/**
* Resolves a sublist of objects starting at fromIndex
up to but not
* including toIndex
. Internally performs bound checking and trims
* indexes accordingly.
*/
protected void resolveInterval(int fromIndex, int toIndex) {
if (fromIndex >= toIndex || elements.isEmpty()) {
return;
}
if (context == null) {
throw new CayenneRuntimeException(
"No ObjectContext set, can't resolve objects.");
}
// bounds checking
if (fromIndex < 0) {
fromIndex = 0;
}
if (toIndex > elements.size()) {
toIndex = elements.size();
}
// find disjoint ranges and resolve them individually...
int fromPage = pageIndex(fromIndex);
int toPage = pageIndex(toIndex - 1);
int rangeStartIndex = -1;
for (int i = fromPage; i <= toPage; i++) {
int pageStartIndex = i * pageSize;
Object firstPageObject = elements.get(pageStartIndex);
if (isUnresolved(firstPageObject)) {
// start range
if (rangeStartIndex < 0) {
rangeStartIndex = pageStartIndex;
}
}
else {
// finish range...
if (rangeStartIndex >= 0) {
forceResolveInterval(rangeStartIndex, pageStartIndex);
rangeStartIndex = -1;
}
}
}
// load last page
if (rangeStartIndex >= 0) {
forceResolveInterval(rangeStartIndex, toIndex);
}
}
void forceResolveInterval(int fromIndex, int toIndex) {
int pastEnd = toIndex - size();
if (pastEnd > 0) {
toIndex = size();
}
int fetchLimit = toIndex - fromIndex;
RangeQuery query = new RangeQuery(cacheKey, fromIndex, fetchLimit, metadata);
List sublist = context.performQuery(query);
// sanity check
if (sublist.size() != fetchLimit) {
throw new CayenneRuntimeException("Resolved range size '"
+ sublist.size()
+ "' is not the same as expected: "
+ fetchLimit);
}
for (int i = 0; i < fetchLimit; i++) {
elements.set(fromIndex + i, sublist.get(i));
}
unfetchedObjects -= sublist.size();
}
/**
* Returns zero-based index of the virtual "page" for a given array element index.
*/
int pageIndex(int elementIndex) {
if (elementIndex < 0 || elementIndex > size()) {
throw new IndexOutOfBoundsException("Index: " + elementIndex);
}
if (pageSize <= 0 || elementIndex < 0) {
return -1;
}
return elementIndex / pageSize;
}
/**
* Returns ObjectContext associated with this list.
*/
public ObjectContext getContext() {
return context;
}
/**
* Returns the pageSize.
*
* @return int
*/
public int getPageSize() {
return pageSize;
}
/**
* Returns a list iterator for this list. DataObjects are resolved a page (according
* to getPageSize()) at a time as necessary - when retrieved with next() or
* previous().
*/
public ListIterator listIterator() {
return new ListIteratorHelper(0);
}
/**
* Returns a list iterator of the elements in this list (in proper sequence), starting
* at the specified position in this list. The specified index indicates the first
* element that would be returned by an initial call to the next method. An initial
* call to the previous method would return the element with the specified index minus
* one. DataObjects are resolved a page at a time (according to getPageSize()) as
* necessary - when retrieved with next() or previous().
*/
public ListIterator listIterator(int index) {
if (index < 0 || index > size()) {
throw new IndexOutOfBoundsException("Index: " + index);
}
return new ListIteratorHelper(index);
}
/**
* Return an iterator for this list. DataObjects are resolved a page (according to
* getPageSize()) at a time as necessary - when retrieved with next().
*/
public Iterator iterator() {
// by virtue of get(index)'s implementation, resolution of ids into
// objects will occur on pageSize boundaries as necessary.
return new Iterator() {
int listIndex = 0;
public boolean hasNext() {
return (listIndex < elements.size());
}
public Object next() {
if (listIndex >= elements.size())
throw new NoSuchElementException("no more elements");
return get(listIndex++);
}
public void remove() {
throw new UnsupportedOperationException("remove not supported.");
}
};
}
/**
* @see java.util.List#add(int, Object)
*/
public void add(int index, Object element) {
helper.validateListObject(element);
elements.add(index, element);
}
/**
* @see java.util.Collection#add(Object)
*/
public boolean add(Object o) {
helper.validateListObject(o);
return elements.add(o);
}
/**
* @see java.util.Collection#addAll(Collection)
*/
public boolean addAll(Collection c) {
return elements.addAll(c);
}
/**
* @see java.util.List#addAll(int, Collection)
*/
public boolean addAll(int index, Collection c) {
return elements.addAll(index, c);
}
/**
* @see java.util.Collection#clear()
*/
public void clear() {
elements.clear();
}
/**
* @see java.util.Collection#contains(Object)
*/
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
/**
* @see java.util.Collection#containsAll(Collection)
*/
public boolean containsAll(Collection c) {
Iterator it = c.iterator();
while (it.hasNext()) {
if (!contains(it.next())) {
return false;
}
}
return true;
}
/**
* @see java.util.List#get(int)
*/
public Object get(int index) {
Object o = elements.get(index);
if (isUnresolved(o)) {
// read this page
int pageStart = pageIndex(index) * pageSize;
resolveInterval(pageStart, pageStart + pageSize);
return elements.get(index);
}
else {
return o;
}
}
/**
* @see java.util.List#indexOf(Object)
*/
public int indexOf(Object o) {
return helper.indexOfObject(o);
}
/**
* @see java.util.Collection#isEmpty()
*/
public boolean isEmpty() {
return elements.isEmpty();
}
/**
* @see java.util.List#lastIndexOf(Object)
*/
public int lastIndexOf(Object o) {
return helper.lastIndexOfObject(o);
}
/**
* @see java.util.List#remove(int)
*/
public Object remove(int index) {
return elements.remove(index);
}
/**
* @see java.util.Collection#remove(Object)
*/
public boolean remove(Object o) {
return elements.remove(o);
}
/**
* @see java.util.Collection#removeAll(Collection)
*/
public boolean removeAll(Collection c) {
return elements.removeAll(c);
}
/**
* @see java.util.Collection#retainAll(Collection)
*/
public boolean retainAll(Collection c) {
return elements.retainAll(c);
}
/**
* @see java.util.List#set(int, Object)
*/
public Object set(int index, Object element) {
helper.validateListObject(element);
return elements.set(index, element);
}
/**
* @see java.util.Collection#size()
*/
public int size() {
return elements.size();
}
public List subList(int fromIndex, int toIndex) {
resolveInterval(fromIndex, toIndex);
return elements.subList(fromIndex, toIndex);
}
public Object[] toArray() {
resolveAll();
return elements.toArray();
}
/**
* @see java.util.Collection#toArray(Object[])
*/
public Object[] toArray(Object[] a) {
resolveAll();
return elements.toArray(a);
}
/**
* Returns a total number of objects that are not resolved yet.
*/
public int getUnfetchedObjects() {
return unfetchedObjects;
}
abstract class ListHelper {
int indexOfObject(Object object) {
if (incorrectObjectType(object)) {
return -1;
}
for (int i = 0; i < elements.size(); i++) {
if (Util.nullSafeEquals(object, get(i))) {
return i;
}
}
return -1;
}
int lastIndexOfObject(Object object) {
if (incorrectObjectType(object)) {
return -1;
}
for (int i = elements.size() - 1; i >= 0; i--) {
if (Util.nullSafeEquals(object, get(i))) {
return i;
}
}
return -1;
}
abstract boolean incorrectObjectType(Object object);
void validateListObject(Object object) throws IllegalArgumentException {
if (incorrectObjectType(object)) {
throw new IllegalArgumentException("Can't store this object: " + object);
}
}
}
class PersistentListHelper extends ListHelper {
boolean incorrectObjectType(Object object) {
if (!(object instanceof Persistent)) {
return true;
}
Persistent persistent = (Persistent) object;
if (persistent.getObjectContext() != context) {
return true;
}
return false;
}
}
class DataRowListHelper extends ListHelper {
boolean incorrectObjectType(Object object) {
if (!(object instanceof Map)) {
return true;
}
Map map = (Map) object;
return map.size() != rowWidth;
}
}
class ListIteratorHelper implements ListIterator {
// by virtue of get(index)'s implementation, resolution of ids into
// objects will occur on pageSize boundaries as necessary.
int listIndex;
public ListIteratorHelper(int startIndex) {
this.listIndex = startIndex;
}
public void add(Object o) {
throw new UnsupportedOperationException("add operation not supported");
}
public boolean hasNext() {
return (listIndex < elements.size());
}
public boolean hasPrevious() {
return (listIndex > 0);
}
public Object next() {
if (listIndex >= elements.size())
throw new NoSuchElementException("at the end of the list");
return get(listIndex++);
}
public int nextIndex() {
return listIndex;
}
public Object previous() {
if (listIndex < 1)
throw new NoSuchElementException("at the beginning of the list");
return get(--listIndex);
}
public int previousIndex() {
return (listIndex - 1);
}
public void remove() {
throw new UnsupportedOperationException("remove operation not supported");
}
public void set(Object o) {
throw new UnsupportedOperationException("set operation not supported");
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy