/* * Copyright 2012 Future Systems * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.krakenapps.eventstorage.engine.file; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.List; import java.util.Map; import java.util.TreeMap; import org.krakenapps.eventstorage.Event; import org.krakenapps.eventstorage.EventRecord; import org.krakenapps.eventstorage.engine.BufferedRandomAccessFile; import org.krakenapps.eventstorage.engine.DatapathUtil; public class EventPointerFile extends FileWriter { private static final long TIMEZONE_OFFSET = Calendar.getInstance().getTimeZone().getRawOffset(); private static final int PTR_SIZE = 10; private static final int FLG_SIZE = 0x1000000 >> 3; private static final int DEFAULT_CACHE_SIZE = 50000; private int key; private FileHandlerManager fileman; private boolean write; private EventFileHeader ptrhdr; private BufferedRandomAccessFile ptrbuf; private Object ptrlock = new Object(); private Map<Integer, Pointer> cache; private int cacheSize; private Object cachelock = new Object(); private byte[] flags = new byte[FLG_SIZE]; private int invalidFlags = 0; public EventPointerFile(int tableId, int key, FileHandlerManager fileman, boolean write) throws IOException { this(tableId, key, fileman, true, DEFAULT_CACHE_SIZE); } public EventPointerFile(int tableId, int key, FileHandlerManager fileman, boolean write, int cacheSize) throws IOException { super(tableId, new File(DatapathUtil.getDirPath(tableId), "data" + key + DatapathUtil.FileType.Pointer)); boolean success = false; try { this.key = key; this.fileman = fileman; this.write = write; this.ptrhdr = getHeader(EventFileHeader.MAGIC_STRING_POINTER); if (!getFile().exists() && !write) throw new FileNotFoundException(); if (getFile().length() == ptrhdr.size()) { byte[] b = Arrays.copyOf(ptrhdr.serialize(), ptrhdr.size() + FLG_SIZE); Arrays.fill(b, ptrhdr.size(), b.length, (byte) 0xFF); FileOutputStream fos = new FileOutputStream(getFile()); fos.write(b); fos.close(); } this.ptrbuf = new BufferedRandomAccessFile(getFile(), write ? "rw" : "r", 4096, ptrhdr.size()); this.ptrbuf.seek(0L); this.ptrbuf.read(flags); for (byte b : flags) { if (b == (byte) 0xFF) invalidFlags++; } this.cache = new TreeMap<Integer, Pointer>(); this.cacheSize = cacheSize; success = true; } finally { if (!success) close(); } } public int getKey() { return key; } public Event read(EventRecord record) throws IOException { touch(); int id = (int) (record.getId() & 0xFFFFFF); Pointer p = cache.get(id); if (p != null) { EventDataFile dat = fileman.getDataFile(getTableId(), getDay(p.day), false); return (dat != null) ? dat.read(p.fp, record) : null; } if (!isValid(id)) return null; short day = 0; long fp = 0; synchronized (ptrlock) { ptrbuf.seek(FLG_SIZE + id * PTR_SIZE); day = ptrbuf.readShort(); fp = ptrbuf.readLong(); } EventDataFile dat = fileman.getDataFile(getTableId(), getDay(day), false); return (dat != null) ? dat.read(fp, record) : null; } public boolean isValid(long id) { int pos = (int) (id & 0xFFFFFF) >>> 3; return ((flags[pos] & 0xFF) & (0x80 >>> (id & 0x7))) == 0; } private void setValidFlag(long id, boolean valid) { modify(); int pos = (int) (id & 0xFFFFFF) >>> 3; if (valid) { if (flags[pos] == (byte) 0xFF) invalidFlags--; flags[pos] = (byte) ((flags[pos] & 0xFF) & ~(0x80 >>> (id & 0x7))); } else { boolean invalid = (flags[pos] == (byte) 0xFF); flags[pos] = (byte) ((flags[pos] & 0xFF) | (0x80 >>> (id & 0x7))); if (!invalid && flags[pos] == (byte) 0xFF) invalidFlags++; } } private Date getDay(short day) { return new Date(((long) day & 0xFFFFL) * 86400000L - TIMEZONE_OFFSET); } public void remove(long id) throws IOException { remove(id, null); } public void remove(long id, Short day) throws IOException { if ((int) ((id >>> 24) & 0xFFFFFF) != key) return; int pos = (int) (id & 0xFFFFFF); if (cache.containsKey(pos)) return; synchronized (ptrlock) { ptrbuf.seek(FLG_SIZE + id * PTR_SIZE); if (day != null && day != ptrbuf.readShort()) return; } setValidFlag(id, false); if (invalidFlags == FLG_SIZE) close(); } @Override protected void doWrite(EventRecord record) throws IOException { if (!write) { write = true; ptrbuf.close(); ptrbuf = new BufferedRandomAccessFile(getFile(), "rw", 4096, ptrhdr.size()); } int id = (int) (record.getId() & 0xFFFFFF); if (!record.isUpdateData() && record.getData() == null) { if (isValid(record.getId())) return; } setValidFlag(id, true); short day = (short) ((record.getDate().getTime() + TIMEZONE_OFFSET) / 86400000L); EventDataFile dat = fileman.getDataFile(getTableId(), record.getDate(), true); long fp = dat.writeAndGetFp(record); synchronized (cachelock) { cache.put(id, new Pointer(id, day, fp)); } if (cache.size() >= cacheSize) flush(true); } protected void doFlush(boolean sync) throws IOException { List<Pointer> ptrs = null; synchronized (cachelock) { ptrs = new ArrayList<Pointer>(cache.values()); cache.clear(); } synchronized (ptrlock) { ptrbuf.seek(0L); ptrbuf.write(flags); for (Pointer p : ptrs) { ptrbuf.seek(FLG_SIZE + p.id * PTR_SIZE); ptrbuf.writeShort(p.day); ptrbuf.writeLong(p.fp); } } ptrbuf.flush(sync); } @Override protected void doClose() { if (ptrbuf != null) ptrbuf.close(); if (invalidFlags == FLG_SIZE) getFile().delete(); } private class Pointer { private int id; private short day; private long fp; private Pointer(int id, short day, long fp) { this.id = id; this.day = day; this.fp = fp; } } @Override public String toString() { return "EventPointerFile [tableId=" + getTableId() + ", key=" + key + "]"; } }