001: package com.jofti.store;
002:
003: import java.io.IOException;
004: import java.nio.ByteBuffer;
005: import java.util.Arrays;
006:
007: import org.apache.commons.logging.Log;
008: import org.apache.commons.logging.LogFactory;
009:
010: import com.jofti.exception.JoftiException;
011:
012: /**
013: *
014: * <p>Each block contains a header, zero or more entries,
015: * and a footer.
016: */
017: class BlockBuffer {
018:
019: ByteBuffer buffer = null;
020:
021: FileStore file = null;
022:
023: // default size
024: int bufferSize = 4096;
025:
026: int recordSize = 0;
027:
028: int numberEntries = 0;
029: /**
030: * Number of used data bytes in the buffer.
031: *
032: * <p>This is different than buffer capacity(). The bytes used is the
033: * number of bytes of usable data in a buffer.
034: *
035: * <p>set by operations that read data from files into the
036: * buffer such as read().
037: * <p>checked by operations that retrieve logical records
038: * from the buffer get().
039: */
040: int bytesUsed = 0;
041:
042: FilePositionHolder positionHolder = null;
043:
044: private static Log log = LogFactory.getLog(BlockBuffer.class);
045:
046: /**
047: * number of times this buffer was used.
048: * <p>In general, should be about the same for all buffers in a pool.
049: */
050: int initCounter = 0;
051:
052: /**
053: * size of a buffer header.
054: * <pre>
055: * <b>buffer Header format</b>
056: * byte[] HEADER_ID [1] "H"
057: * short size of bytes written [2]
058: * short total number of items [2]
059: * byte[] CRLF [2] to make it easier to read buffers in an editor
060: * </pre>
061: */
062: final static int bufferHeaderSize = 7;
063:
064: final int bytesUsedOffSet = 1;
065:
066: /**
067: * The number of bytes to reserve for block footer information.
068: */
069: final static int bufferFooterSize = 3;
070:
071: /*
072: * byte FOOTER_ID [1] "F"
073: * byte CRLF [2] to make it easier to read buffers in an editor
074: */
075:
076: /**
077: * Size of the header for each data record in the block.
078: *
079: * <p>Record header format:
080: * int record length of user data [4]
081: */
082: private int recordHeaderSize = 4;
083:
084: // block header & footer fields
085: /**
086: * Carriage Return Line Feed sequence used for debugging purposes.
087: */
088: private byte[] CRLF = "\r\n".getBytes();
089:
090: private byte[] crlf = new byte[CRLF.length];
091:
092: /**
093: * Signature for each logical block header.
094: */
095: private byte[] HEADER_ID = "H".getBytes();
096:
097: /**
098: * Signature for each logical block footer.
099: */
100: private byte[] FOOTER_ID = "F".getBytes();
101:
102: /**
103: * maximum size of user data record.
104: *
105: * <p>Although this member is local to a LogBuffer instance,
106: * it is assumed to have the same value in all instances.
107: */
108: private int payloadSize;
109:
110: /**
111: * default constructor calls super class constructor.
112: */
113: BlockBuffer() {
114:
115: }
116:
117: public int remaining() {
118: return buffer.remaining() - (bufferFooterSize);
119: }
120:
121: /**
122: * initialize members for buffer reuse.
123: *
124: * @param bsn Logic Block Sequence Number of the buffer.
125: * BufferManager maintains a list of block sequence numbers
126: * to ensure correct order of writes to disk. Some implementations
127: * of LogBuffer may include the BSN as part of a record or
128: * block header.
129: */
130: public void init(int bufferSize) throws JoftiException {
131: // sun Bug ID: 4879883 - can't allocate more than 64mb without resetting
132: // in jdk earlier than 1.4.2_05 - workaround -XX:MaxDirectMemorySize=XXXM
133:
134: buffer = ByteBuffer.allocateDirect(bufferSize);
135:
136: ++initCounter;
137:
138: buffer.clear();
139:
140: payloadSize = ((bufferSize) - (bufferHeaderSize + bufferFooterSize));
141:
142: }
143:
144: public void reset() {
145: buffer.clear();
146: positionHolder = null;
147: file = null;
148: recordSize = 0;
149: bytesUsed = 0;
150:
151: }
152:
153: void writeFurniture(int numberRecords) {
154: buffer.put(HEADER_ID);
155:
156: // placeholder for length of bytes in record
157: buffer.putShort((short) 0);
158: buffer.putShort((short) numberRecords);
159: buffer.put(CRLF);
160:
161: }
162:
163: int put(ByteBuffer buf, int offset) throws JoftiException {
164:
165: int written = 0;
166:
167: int remaining = buffer.remaining() - bufferFooterSize;
168:
169: // write the length of the array element
170: // ensure that it is first element of array - or we do not write this
171: if (offset == 0 && recordHeaderSize <= remaining) {
172: // buffer.putInt(data.length);
173: remaining -= 4;
174: } else if (offset == 0 && recordHeaderSize > remaining) {
175: // we can ignore this as there is not enough space
176: // to write even the header
177: return written;
178: }
179: // we do this to make sure that we always have the size in front of the array
180:
181: // try and write some bytes from the data - this could be partial for a large array
182: if (remaining > 0) {
183:
184: int length = remaining;
185: // can we fit all the data element into the buffer
186: // if ((data.length - offset) < length) {
187: // length = data.length - offset;
188: // }
189: // buffer.put(data, offset, length);
190: // update how much we have written
191: written = length;
192: // update what we have remaining
193:
194: }
195:
196: bytesUsed = buffer.position();
197: return written;
198: }
199:
200: void put(ByteBuffer buf) throws JoftiException {
201:
202: // write the length of the array element
203: // ensure that it is first element of array - or we do not write this
204: buffer.put(buf);
205: // update how much we have written
206:
207: bytesUsed = buffer.position();
208: }
209:
210: void writeFooter() {
211: if (buffer.position() != payloadSize + bufferHeaderSize) {
212: buffer.position(payloadSize + bufferHeaderSize);
213: }
214:
215: buffer.put(FOOTER_ID);
216: buffer.put(CRLF);
217:
218: }
219:
220: /**
221: * write ByteBuffer to the log file.
222: */
223: void write() throws IOException {
224: // assert lf != null: "FileStore lf is null";
225:
226: //write bytesUsed
227: buffer.putShort(bytesUsedOffSet, (short) bytesUsed);
228:
229: writeFooter();
230: try {
231:
232: buffer.flip();
233: file.write(this );
234:
235: } catch (IOException e) {
236: throw e;
237: }
238: }
239:
240: /**
241: * Reads a block from FileStore <i> lf </i> and validates
242: * header and footer information.
243: *
244: * @see LogBuffer#read(FileStore, long)
245: * @throws IOException
246: * if anything goes wrong during the file read.
247: * @throws InvalidLogBufferException
248: * if any of the block header or footer fields are invalid.
249: */
250: BlockBuffer read(FileStore store) throws JoftiException {
251: long position = positionHolder.position;
252:
253: try {
254: // fill our ByteBuffer with a block of data from the file
255: int bytesRead = -1;
256: if (store.channel.size() > position) // JRockit issue identified in HOWL
257: bytesRead = store.channel.read(buffer, position);
258: if (bytesRead == -1) {
259: // end of file
260: return null;
261: }
262:
263: if (bytesRead != buffer.capacity())
264: throw new JoftiException("FILESIZE Error: bytesRead="
265: + bytesRead);
266:
267: // verify header
268: buffer.clear();
269: byte head = buffer.get();
270: if (head != HEADER_ID[0])
271: throw new JoftiException(
272: "HEADER_ID does not match for position "
273: + position + " in store "
274: + store.fileId);
275:
276: // get data used (short)
277: bytesUsed = buffer.getShort();
278: if (bytesUsed < 0)
279: throw new JoftiException("data used: " + bytesUsed
280: + " for position " + position + " in store "
281: + store.fileId);
282: // get the number of records
283: numberEntries = buffer.getShort();
284: if (numberEntries < 0)
285: throw new JoftiException("entries: " + numberEntries
286: + " for position " + position + " in store "
287: + store.fileId);
288:
289: // get CRLF
290: buffer.get(crlf);
291: if (!Arrays.equals(crlf, CRLF))
292: throw new JoftiException(
293: "HEADER_CRLF not present for position "
294: + position + " in store "
295: + store.fileId);
296:
297: // mark start of first data record
298: buffer.mark();
299:
300: // get FOOTER_ID and compare
301: buffer.position(payloadSize + bufferHeaderSize);
302:
303: byte footer = buffer.get();
304: if (footer != FOOTER_ID[0]) {
305: throw new JoftiException(
306: "FOOTER_ID not present for position "
307: + position + " in store "
308: + store.fileId);
309: }
310:
311: // get FOOTER_CRLF
312: buffer.get(crlf);
313: if (!Arrays.equals(crlf, CRLF))
314: throw new JoftiException(
315: "FOOTER_CRLF not found for position "
316: + position + " in store "
317: + store.fileId);
318:
319: //set the limit
320: buffer.limit(bytesUsed);
321: // reset position to first data record
322:
323: buffer.reset();
324: } catch (Exception e) {
325: throw new JoftiException(e);
326: }
327: return this ;
328: }
329:
330: /**
331: * return statistics for this buffer.
332: *
333: * @return String containing statistics as an XML node
334: */
335: String getStats() {
336: String name = this .getClass().getName();
337:
338: String result = "<LogBuffer class='"
339: + name
340: + "\n <timesUsed value='"
341: + initCounter
342: + "'>Number of times this buffer was initialized for use</timesUsed>"
343: + "\n</LogBuffer>" + "\n";
344:
345: return result;
346: }
347:
348: public FileStore getFileStore() {
349: return file;
350: }
351:
352: public void setFileStore(FileStore file) {
353: this .file = file;
354: }
355:
356: public FilePositionHolder getPositionHolder() {
357: return positionHolder;
358: }
359:
360: public void setPositionHolder(FilePositionHolder positionHolder) {
361: this.positionHolder = positionHolder;
362: }
363: }
|