001: /*
002: * Copyright 2004-2006 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.apache.lucene.store.jdbc.index;
018:
019: import java.io.IOException;
020: import java.sql.Blob;
021: import java.sql.PreparedStatement;
022: import java.sql.ResultSet;
023:
024: import org.apache.lucene.store.jdbc.JdbcDirectory;
025: import org.apache.lucene.store.jdbc.JdbcFileEntrySettings;
026: import org.apache.lucene.store.jdbc.JdbcStoreException;
027: import org.apache.lucene.store.jdbc.support.JdbcTemplate;
028:
029: /**
030: * An <code>IndexInput</code> implementation, that for every buffer refill will go and fetch
031: * the data from the database.
032: *
033: * @author kimchy
034: */
035: public class FetchOnBufferReadJdbcIndexInput extends
036: JdbcBufferedIndexInput {
037:
038: private String name;
039:
040: // lazy intialize the length
041: private long totalLength = -1;
042:
043: private long position = 1;
044:
045: private JdbcDirectory jdbcDirectory;
046:
047: public void configure(String name, JdbcDirectory jdbcDirectory,
048: JdbcFileEntrySettings settings) throws IOException {
049: super .configure(name, jdbcDirectory, settings);
050: this .jdbcDirectory = jdbcDirectory;
051: this .name = name;
052: }
053:
054: // Overriding refill here since we can execute a single query to get both the length and the buffer data
055: // resulted in not the nicest OO design, where the buffer information is protected in the JdbcBufferedIndexInput class
056: // and code duplication between this method and JdbcBufferedIndexInput.
057: // Performance is much better this way!
058: protected void refill() throws IOException {
059: jdbcDirectory.getJdbcTemplate().executeSelect(
060: jdbcDirectory.getTable().sqlSelectSizeValueByName(),
061: new JdbcTemplate.ExecuteSelectCallback() {
062: public void fillPrepareStatement(
063: PreparedStatement ps) throws Exception {
064: ps.setFetchSize(1);
065: ps.setString(1, name);
066: }
067:
068: public Object execute(ResultSet rs)
069: throws Exception {
070: // START read blob and update length if required
071: if (!rs.next()) {
072: throw new JdbcStoreException(
073: "No entry for [" + name
074: + "] table "
075: + jdbcDirectory.getTable());
076: }
077: synchronized (this ) {
078: if (totalLength == -1) {
079: totalLength = rs.getLong(3);
080: }
081: }
082: // END read blob and update length if required
083:
084: long start = bufferStart + bufferPosition;
085: long end = start + bufferSize;
086: if (end > length()) // don't read past EOF
087: end = length();
088: bufferLength = (int) (end - start);
089: if (bufferLength <= 0)
090: throw new IOException("read past EOF");
091:
092: if (buffer == null) {
093: buffer = new byte[bufferSize]; // allocate buffer lazily
094: seekInternal(bufferStart);
095: }
096: // START replace read internal
097: Blob blob = rs.getBlob(2);
098: readInternal(blob, buffer, 0, bufferLength);
099:
100: bufferStart = start;
101: bufferPosition = 0;
102: return null;
103: }
104: });
105: }
106:
107: protected synchronized void readInternal(final byte[] b,
108: final int offset, final int length) throws IOException {
109: jdbcDirectory.getJdbcTemplate().executeSelect(
110: jdbcDirectory.getTable().sqlSelectSizeValueByName(),
111: new JdbcTemplate.ExecuteSelectCallback() {
112: public void fillPrepareStatement(
113: PreparedStatement ps) throws Exception {
114: ps.setFetchSize(1);
115: ps.setString(1, name);
116: }
117:
118: public Object execute(ResultSet rs)
119: throws Exception {
120: if (!rs.next()) {
121: throw new JdbcStoreException(
122: "No entry for [" + name
123: + "] table "
124: + jdbcDirectory.getTable());
125: }
126: Blob blob = rs.getBlob(2);
127: readInternal(blob, b, offset, length);
128: synchronized (this ) {
129: if (FetchOnBufferReadJdbcIndexInput.this .totalLength == -1) {
130: FetchOnBufferReadJdbcIndexInput.this .totalLength = rs
131: .getLong(3);
132: }
133: }
134: return null;
135: }
136: });
137: }
138:
139: /**
140: * A helper methods that already reads an open blob
141: */
142: private synchronized void readInternal(Blob blob, final byte[] b,
143: final int offset, final int length) throws Exception {
144: long curPos = getFilePointer();
145: if (curPos + 1 != position) {
146: position = curPos + 1;
147: }
148: byte[] bytesRead = blob.getBytes(position, length);
149: if (bytesRead.length != length) {
150: throw new IOException("read past EOF");
151: }
152: System.arraycopy(bytesRead, 0, b, offset, length);
153: position += bytesRead.length;
154: }
155:
156: protected void seekInternal(long pos) throws IOException {
157: this .position = pos + 1;
158: }
159:
160: public void close() throws IOException {
161: }
162:
163: public synchronized long length() {
164: if (totalLength == -1) {
165: try {
166: this .totalLength = jdbcDirectory.fileLength(name);
167: } catch (IOException e) {
168: // do nothing here for now, much better for performance
169: }
170: }
171: return totalLength;
172: }
173: }
|