View Javadoc
1   /*
2    *    Copyright 2009-2023 the original author or authors.
3    *
4    *    Licensed under the Apache License, Version 2.0 (the "License");
5    *    you may not use this file except in compliance with the License.
6    *    You may obtain a copy of the License at
7    *
8    *       https://www.apache.org/licenses/LICENSE-2.0
9    *
10   *    Unless required by applicable law or agreed to in writing, software
11   *    distributed under the License is distributed on an "AS IS" BASIS,
12   *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *    See the License for the specific language governing permissions and
14   *    limitations under the License.
15   */
16  package org.apache.ibatis.cursor.defaults;
17  
18  import java.sql.ResultSet;
19  import java.sql.SQLException;
20  import java.util.Iterator;
21  import java.util.NoSuchElementException;
22  
23  import org.apache.ibatis.cursor.Cursor;
24  import org.apache.ibatis.executor.resultset.DefaultResultSetHandler;
25  import org.apache.ibatis.executor.resultset.ResultSetWrapper;
26  import org.apache.ibatis.mapping.ResultMap;
27  import org.apache.ibatis.session.ResultContext;
28  import org.apache.ibatis.session.ResultHandler;
29  import org.apache.ibatis.session.RowBounds;
30  
31  /**
32   * This is the default implementation of a MyBatis Cursor. This implementation is not thread safe.
33   *
34   * @author Guillaume Darmont / guillaume@dropinocean.com
35   */
36  public class DefaultCursor<T> implements Cursor<T> {
37  
38    // ResultSetHandler stuff
39    private final DefaultResultSetHandler resultSetHandler;
40    private final ResultMap resultMap;
41    private final ResultSetWrapper rsw;
42    private final RowBounds rowBounds;
43    protected final ObjectWrapperResultHandler<T> objectWrapperResultHandler = new ObjectWrapperResultHandler<>();
44  
45    private final CursorIterator cursorIterator = new CursorIterator();
46    private boolean iteratorRetrieved;
47  
48    private CursorStatus status = CursorStatus.CREATED;
49    private int indexWithRowBound = -1;
50  
51    private enum CursorStatus {
52  
53      /**
54       * A freshly created cursor, database ResultSet consuming has not started.
55       */
56      CREATED,
57      /**
58       * A cursor currently in use, database ResultSet consuming has started.
59       */
60      OPEN,
61      /**
62       * A closed cursor, not fully consumed.
63       */
64      CLOSED,
65      /**
66       * A fully consumed cursor, a consumed cursor is always closed.
67       */
68      CONSUMED
69    }
70  
71    public DefaultCursor(DefaultResultSetHandler resultSetHandler, ResultMap resultMap, ResultSetWrapper rsw,
72        RowBounds rowBounds) {
73      this.resultSetHandler = resultSetHandler;
74      this.resultMap = resultMap;
75      this.rsw = rsw;
76      this.rowBounds = rowBounds;
77    }
78  
79    @Override
80    public boolean isOpen() {
81      return status == CursorStatus.OPEN;
82    }
83  
84    @Override
85    public boolean isConsumed() {
86      return status == CursorStatus.CONSUMED;
87    }
88  
89    @Override
90    public int getCurrentIndex() {
91      return rowBounds.getOffset() + cursorIterator.iteratorIndex;
92    }
93  
94    @Override
95    public Iterator<T> iterator() {
96      if (iteratorRetrieved) {
97        throw new IllegalStateException("Cannot open more than one iterator on a Cursor");
98      }
99      if (isClosed()) {
100       throw new IllegalStateException("A Cursor is already closed.");
101     }
102     iteratorRetrieved = true;
103     return cursorIterator;
104   }
105 
106   @Override
107   public void close() {
108     if (isClosed()) {
109       return;
110     }
111 
112     ResultSet rs = rsw.getResultSet();
113     try {
114       if (rs != null) {
115         rs.close();
116       }
117     } catch (SQLException e) {
118       // ignore
119     } finally {
120       status = CursorStatus.CLOSED;
121     }
122   }
123 
124   protected T fetchNextUsingRowBound() {
125     T result = fetchNextObjectFromDatabase();
126     while (objectWrapperResultHandler.fetched && indexWithRowBound < rowBounds.getOffset()) {
127       result = fetchNextObjectFromDatabase();
128     }
129     return result;
130   }
131 
132   protected T fetchNextObjectFromDatabase() {
133     if (isClosed()) {
134       return null;
135     }
136 
137     try {
138       objectWrapperResultHandler.fetched = false;
139       status = CursorStatus.OPEN;
140       if (!rsw.getResultSet().isClosed()) {
141         resultSetHandler.handleRowValues(rsw, resultMap, objectWrapperResultHandler, RowBounds.DEFAULT, null);
142       }
143     } catch (SQLException e) {
144       throw new RuntimeException(e);
145     }
146 
147     T next = objectWrapperResultHandler.result;
148     if (objectWrapperResultHandler.fetched) {
149       indexWithRowBound++;
150     }
151     // No more object or limit reached
152     if (!objectWrapperResultHandler.fetched || getReadItemsCount() == rowBounds.getOffset() + rowBounds.getLimit()) {
153       close();
154       status = CursorStatus.CONSUMED;
155     }
156     objectWrapperResultHandler.result = null;
157 
158     return next;
159   }
160 
161   private boolean isClosed() {
162     return status == CursorStatus.CLOSED || status == CursorStatus.CONSUMED;
163   }
164 
165   private int getReadItemsCount() {
166     return indexWithRowBound + 1;
167   }
168 
169   protected static class ObjectWrapperResultHandler<T> implements ResultHandler<T> {
170 
171     protected T result;
172     protected boolean fetched;
173 
174     @Override
175     public void handleResult(ResultContext<? extends T> context) {
176       this.result = context.getResultObject();
177       context.stop();
178       fetched = true;
179     }
180   }
181 
182   protected class CursorIterator implements Iterator<T> {
183 
184     /**
185      * Holder for the next object to be returned.
186      */
187     T object;
188 
189     /**
190      * Index of objects returned using next(), and as such, visible to users.
191      */
192     int iteratorIndex = -1;
193 
194     @Override
195     public boolean hasNext() {
196       if (!objectWrapperResultHandler.fetched) {
197         object = fetchNextUsingRowBound();
198       }
199       return objectWrapperResultHandler.fetched;
200     }
201 
202     @Override
203     public T next() {
204       // Fill next with object fetched from hasNext()
205       T next = object;
206 
207       if (!objectWrapperResultHandler.fetched) {
208         next = fetchNextUsingRowBound();
209       }
210 
211       if (objectWrapperResultHandler.fetched) {
212         objectWrapperResultHandler.fetched = false;
213         object = null;
214         iteratorIndex++;
215         return next;
216       }
217       throw new NoSuchElementException();
218     }
219 
220     @Override
221     public void remove() {
222       throw new UnsupportedOperationException("Cannot remove element from Cursor");
223     }
224   }
225 }