/******************************************************************************* * Cloud Foundry * Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved. * * This product is licensed to you under the Apache License, Version 2.0 (the "License"). * You may not use this product except in compliance with the License. * * This product includes a number of subcomponents with * separate copyright notices and license terms. Your use of these * subcomponents is subject to the terms and conditions of the * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ package org.cloudfoundry.identity.uaa.resources.jdbc; import java.util.AbstractList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; /** * <p> * List implementation backed by a database query, allowing iteration and * sublist operations without pulling the wole dataset into memory. * </p> * * <p> * Not thread safe. * </p> * * @author Dave Syer * */ public class JdbcPagingList<E> extends AbstractList<E> { private final int size; private int start = 0; private List<E> current; private final int pageSize; private final RowMapper<E> mapper; private final Map<String, ?> args; private final String sql; private final NamedParameterJdbcTemplate parameterJdbcTemplate; private final LimitSqlAdapter limitSqlAdapter; public JdbcPagingList(JdbcTemplate jdbTemplate, LimitSqlAdapter limitSqlAdapter, String sql, RowMapper<E> mapper, int pageSize) { this(jdbTemplate, limitSqlAdapter, sql, Collections.<String, Object> emptyMap(), mapper, pageSize); } public JdbcPagingList(JdbcTemplate jdbcTemplate, LimitSqlAdapter limitSqlAdapter, String sql, Map<String, ?> args, RowMapper<E> mapper, int pageSize) { this(new NamedParameterJdbcTemplate(jdbcTemplate), limitSqlAdapter, sql, args, mapper, pageSize); } public JdbcPagingList(NamedParameterJdbcTemplate jdbcTemplate, LimitSqlAdapter limitSqlAdapter, String sql, Map<String, ?> args, RowMapper<E> mapper, int pageSize) { this.parameterJdbcTemplate = jdbcTemplate; this.sql = sql; this.args = args; this.mapper = mapper; this.size = parameterJdbcTemplate.queryForObject(getCountSql(sql), args, Integer.class); this.pageSize = pageSize; this.limitSqlAdapter = limitSqlAdapter; } @Override public E get(int index) { if (index >= size) { throw new ArrayIndexOutOfBoundsException(index); } if (current == null || index - start >= pageSize || index < start) { current = parameterJdbcTemplate.query(limitSqlAdapter.getLimitSql(sql, index, pageSize), args, mapper); start = index; } return current.get(index - start); } @Override public Iterator<E> iterator() { return new SafeIterator<E>(super.iterator()); } @Override public List<E> subList(int fromIndex, int toIndex) { if (fromIndex < 0 || toIndex > size || fromIndex > toIndex) { throw new IndexOutOfBoundsException("The indexes provided are outside the bounds of this list."); } return new SafeIteratorList<E>(super.subList(fromIndex, toIndex)); } private String getCountSql(String sql) { String result = sql.replaceAll("(?i)select (.*?) from (.*)", "select count(*) from $2"); int orderByPos = result.toLowerCase().lastIndexOf("order by"); if (orderByPos >= 0) { result = result.substring(0, orderByPos); } return result; } @Override public int size() { return this.size; } /** * <p> * A list whose iterators are safe from changes in the underlying list. The * size is not always accurate if the underlying list changes, but the * iterator will never say it has more elements and then fail when iterated. * </p> * * <p> * Not thread safe. * </p> * * @author Dave Syer * * @param <T> the element type */ private static class SafeIteratorList<T> extends AbstractList<T> { private final List<T> list; public SafeIteratorList(List<T> list) { this.list = list; } @Override public Iterator<T> iterator() { return new SafeIterator<T>(super.iterator()); } @Override public T get(int index) { return list.get(index); } @Override public int size() { return list.size(); } } private static class SafeIterator<T> implements Iterator<T> { private final Iterator<T> iterator; private boolean polled = false; private boolean hasNext = false; private T next; public SafeIterator(Iterator<T> iterator) { this.iterator = iterator; } @Override public boolean hasNext() { if (!polled) { polled = true; try { next = iterator.next(); hasNext = true; return true; } catch (NoSuchElementException e) { hasNext = false; return false; } } return hasNext; } @Override public T next() { if (hasNext()) { polled = false; return next; } throw new NoSuchElementException(); } @Override public void remove() { throw new UnsupportedOperationException("Not supported: readonly interator"); } } }