1 /***
2 *
3 * Copyright 2004 Hiram Chirino
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 **/
18 package org.activeio.journal.active;
19
20 import java.io.DataInput;
21 import java.io.DataInputStream;
22 import java.io.DataOutput;
23 import java.io.DataOutputStream;
24 import java.io.IOException;
25 import java.util.zip.CRC32;
26
27 import org.activeio.Disposable;
28 import org.activeio.Packet;
29 import org.activeio.adapter.PacketInputStream;
30 import org.activeio.adapter.PacketOutputStream;
31 import org.activeio.packet.ByteArrayPacket;
32
33
34 /***
35 * Serializes/Deserializes data records.
36 *
37 * @version $Revision: 1.1 $
38 */
39 final public class Record implements Disposable {
40
41 static final public int RECORD_HEADER_SIZE=8+Location.SERIALIZED_SIZE;
42 static final public int RECORD_FOOTER_SIZE=12+Location.SERIALIZED_SIZE;
43 static final public int RECORD_BASE_SIZE=RECORD_HEADER_SIZE+RECORD_FOOTER_SIZE;
44
45 static final public byte[] START_OF_RECORD = new byte[] { 'S', 'o', 'R' };
46 static final public byte[] END_OF_RECORD = new byte[] { 'E', 'o', 'R', '.' };
47
48 static final public int SELECTED_CHECKSUM_ALGORITHIM;
49 static final public int NO_CHECKSUM_ALGORITHIM=0;
50 static final public int HASH_CHECKSUM_ALGORITHIM=1;
51 static final public int CRC32_CHECKSUM_ALGORITHIM=2;
52
53 static {
54 String type = System.getProperty("org.activeio.journal.active.SELECTED_CHECKSUM_ALGORITHIM", "none");
55 if( "none".equals(type) ) {
56 SELECTED_CHECKSUM_ALGORITHIM = NO_CHECKSUM_ALGORITHIM;
57 } else if( "crc32".equals(type) ) {
58 SELECTED_CHECKSUM_ALGORITHIM = CRC32_CHECKSUM_ALGORITHIM;
59 } else if( "hash".equals(type) ) {
60 SELECTED_CHECKSUM_ALGORITHIM = HASH_CHECKSUM_ALGORITHIM;
61 } else {
62 System.err.println("System property 'org.activeio.journal.active.SELECTED_CHECKSUM_ALGORITHIM' not set properly. Valid values are: 'none', 'hash', or 'crc32'");
63 SELECTED_CHECKSUM_ALGORITHIM = NO_CHECKSUM_ALGORITHIM;
64 }
65 }
66
67 static public boolean isChecksumingEnabled() {
68 return SELECTED_CHECKSUM_ALGORITHIM!=NO_CHECKSUM_ALGORITHIM;
69 }
70
71 private final ByteArrayPacket headerFooterPacket = new ByteArrayPacket(new byte[RECORD_BASE_SIZE]);
72 private final DataOutputStream headerFooterData = new DataOutputStream(new PacketOutputStream(headerFooterPacket));
73
74 private int payloadLength;
75 private Location location;
76 private byte recordType;
77 private long checksum;
78 private Location mark;
79 private Packet payload;
80
81 public Record() {
82 }
83
84 public Record(byte recordType, Packet payload, Location mark) throws IOException {
85 this(null, recordType, payload, mark);
86 }
87
88 public Record(Location location, byte recordType, Packet payload, Location mark) throws IOException {
89 this.location = location;
90 this.recordType = recordType;
91 this.mark = mark;
92 this.payload = payload.slice();
93 this.payloadLength = payload.remaining();
94 if( isChecksumingEnabled() ) {
95 checksum(new DataInputStream(new PacketInputStream(this.payload)));
96 }
97
98 writeHeader(headerFooterData);
99 writeFooter(headerFooterData);
100 }
101
102 public void setLocation(Location location) throws IOException {
103 this.location = location;
104 headerFooterPacket.clear();
105 headerFooterPacket.position(8);
106 location.writeToDataOutput(headerFooterData);
107 headerFooterPacket.position(RECORD_HEADER_SIZE+8);
108 location.writeToDataOutput(headerFooterData);
109 payload.clear();
110 headerFooterPacket.position(0);
111 headerFooterPacket.limit(RECORD_HEADER_SIZE);
112 }
113
114 private void writeHeader( DataOutput out ) throws IOException {
115 out.write(START_OF_RECORD);
116 out.writeByte(recordType);
117 out.writeInt(payloadLength);
118 if( location!=null )
119 location.writeToDataOutput(out);
120 else
121 out.writeLong(0);
122 }
123
124 public void readHeader( DataInput in ) throws IOException {
125 readAndCheckConstant(in, START_OF_RECORD, "Invalid record header: start of record constant missing.");
126 recordType = in.readByte();
127 payloadLength = in.readInt();
128 if( payloadLength < 0 )
129 throw new IOException("Invalid record header: record length cannot be less than zero.");
130 location = Location.readFromDataInput(in);
131 }
132
133 private void writeFooter( DataOutput out ) throws IOException {
134 out.writeLong(checksum);
135 if( location!=null )
136 location.writeToDataOutput(out);
137 else
138 out.writeLong(0);
139 out.write(END_OF_RECORD);
140 }
141
142 public void readFooter( DataInput in ) throws IOException {
143 long l = in.readLong();
144 if( isChecksumingEnabled() ) {
145 if( l!=checksum )
146 throw new IOException("Invalid record footer: checksum does not match.");
147 } else {
148 checksum = l;
149 }
150
151 Location loc = Location.readFromDataInput(in);
152 if( !loc.equals(location) )
153 throw new IOException("Invalid record footer: location id does not match.");
154
155 readAndCheckConstant(in, END_OF_RECORD, "Invalid record header: end of record constant missing.");
156 }
157
158 /***
159 * @param randomAccessFile
160 * @throws IOException
161 */
162 public void checksum(DataInput in) throws IOException {
163 if( SELECTED_CHECKSUM_ALGORITHIM==HASH_CHECKSUM_ALGORITHIM ) {
164
165 byte buffer[] = new byte[1024];
166 byte rc[] = new byte[8];
167 for (int i = 0; i < payloadLength;) {
168 int l = Math.min(buffer.length, payloadLength-i);
169 in.readFully(buffer,0,l);
170 for (int j = 0; j < l; j++) {
171 rc[j%8] ^= buffer[j];
172 }
173 i+=l;
174 }
175 checksum = (rc[0])|(rc[1]<<1)|(rc[2]<<2)|(rc[3]<<3)|(rc[4]<<4)|(rc[5]<<5)|(rc[6]<<6)|(rc[7]<<7) ;
176
177 } else if( SELECTED_CHECKSUM_ALGORITHIM==CRC32_CHECKSUM_ALGORITHIM ) {
178 byte buffer[] = new byte[1024];
179 CRC32 crc32 = new CRC32();
180 for (int i = 0; i < payloadLength;) {
181 int l = Math.min(buffer.length, payloadLength-i);
182 in.readFully(buffer,0,l);
183 crc32.update(buffer,0,l);
184 i+=l;
185 }
186 checksum = crc32.getValue();
187 } else {
188 checksum = 0L;
189 }
190 }
191
192
193 /***
194 */
195 private void readAndCheckConstant(DataInput in, byte[] byteConstant, String errorMessage ) throws IOException {
196 for (int i = 0; i < byteConstant.length; i++) {
197 byte checkByte = byteConstant[i];
198 if( in.readByte()!= checkByte ) {
199 throw new IOException(errorMessage);
200 }
201 }
202 }
203
204 public boolean readFromPacket(Packet packet) throws IOException {
205 Packet dup = packet.duplicate();
206
207 if( dup.remaining() < RECORD_HEADER_SIZE )
208 return false;
209 DataInputStream is = new DataInputStream(new PacketInputStream(dup));
210 readHeader( is );
211 if( dup.remaining() < payloadLength+RECORD_FOOTER_SIZE ) {
212 return false;
213 }
214
215
216 dup.limit(dup.position()+payloadLength);
217 this.payload = dup.slice();
218 if( isChecksumingEnabled() ) {
219 checksum(new DataInputStream(new PacketInputStream(payload)));
220 }
221
222
223 dup.limit(packet.limit());
224 dup.position(dup.position()+payloadLength);
225 readFooter(is);
226
227
228 packet.position(dup.position());
229 dup.dispose();
230 return true;
231 }
232
233 /***
234 * @return Returns the checksum.
235 */
236 public long getChecksum() {
237 return checksum;
238 }
239
240 /***
241 * @return Returns the length.
242 */
243 public int getPayloadLength() {
244 return payloadLength;
245 }
246
247 /***
248 * @return Returns the length of the record .
249 */
250 public int getRecordLength() {
251 return payloadLength+Record.RECORD_BASE_SIZE;
252 }
253
254 /***
255 * @return Returns the location.
256 */
257 public Location getLocation() {
258 return location;
259 }
260
261 /***
262 * @return Returns the mark.
263 */
264 public Location getMark() {
265 return mark;
266 }
267
268 /***
269 * @return Returns the payload.
270 */
271 public Packet getPayload() {
272 return payload;
273 }
274
275 /***
276 * @return Returns the recordType.
277 */
278 public byte getRecordType() {
279 return recordType;
280 }
281
282 public boolean hasRemaining() {
283 return headerFooterPacket.position()!=RECORD_BASE_SIZE;
284 }
285
286 public void read(Packet packet) {
287
288
289 headerFooterPacket.read(packet);
290
291 payload.read(packet);
292
293
294 if( !payload.hasRemaining() && headerFooterPacket.position()==RECORD_HEADER_SIZE ) {
295 headerFooterPacket.position(RECORD_HEADER_SIZE);
296 headerFooterPacket.limit(RECORD_BASE_SIZE);
297 headerFooterPacket.read(packet);
298 }
299
300 }
301
302 public void dispose() {
303 if( payload!=null ) {
304 payload.dispose();
305 payload=null;
306 }
307 }
308
309 }