package com.knowgate.berkeleydb; import java.io.FileNotFoundException; import java.util.Date; import java.util.HashMap; import java.util.Random; import java.text.SimpleDateFormat; import java.sql.Types; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Arrays; import java.util.ArrayList; import java.util.LinkedList; import com.knowgate.debug.DebugFile; import com.knowgate.debug.StackTraceUtil; import com.knowgate.misc.Gadgets; import com.knowgate.misc.NameValuePair; import com.knowgate.storage.DataSource; import com.knowgate.storage.Table; import com.knowgate.storage.Column; import com.knowgate.storage.Record; import com.knowgate.storage.RecordSet; import com.knowgate.storage.AbstractRecord; import com.knowgate.storage.StorageException; import com.knowgate.storage.IntegrityViolationException; import com.sleepycat.db.Cursor; import com.sleepycat.db.JoinCursor; import com.sleepycat.db.JoinConfig; import com.sleepycat.db.Database; import com.sleepycat.db.LockMode; import com.sleepycat.db.Transaction; import com.sleepycat.db.DatabaseType; import com.sleepycat.db.OperationStatus; import com.sleepycat.db.DatabaseEntry; import com.sleepycat.db.SecondaryConfig; import com.sleepycat.db.SecondaryCursor; import com.sleepycat.db.SecondaryDatabase; import com.sleepycat.db.DatabaseException; import com.sleepycat.db.DeadlockException; import com.sleepycat.bind.EntryBinding; import com.sleepycat.bind.serial.StoredClassCatalog; public class DBTable implements Table { private String sCnm; private String sDbk; private DBEnvironment oRep; private Database oPdb; private Database oFdb; private HashMap<String,DBIndex> oInd; private StoredClassCatalog oCtg; private EntryBinding oKey; private Transaction oTrn; private static SimpleDateFormat oTsFmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @SuppressWarnings("unused") private DBTable() { } // -------------------------------------------------------------------------- protected DBTable(DBEnvironment oDatabaseEnvironment, String sConnectionName, String sDatabaseName, Database oPrimaryDatabase, HashMap<String,DBIndex> oIndexes, Database oForeignDatabase, StoredClassCatalog oClassCatalog, EntryBinding oEntryBind) throws StorageException { sCnm = sConnectionName; oRep = oDatabaseEnvironment; sDbk = sDatabaseName; oPdb = oPrimaryDatabase; oInd = oIndexes; oFdb = oForeignDatabase; oCtg = oClassCatalog; oKey = oEntryBind; try { if (oRep.isTransactional()) oTrn = oRep.getEnvironment().beginTransaction(null,null); else oTrn = null; } catch (DatabaseException dbe) { throw new StorageException(dbe.getMessage(),dbe); } if (!isReadOnly()) { for (String sColumnName : oInd.keySet()) { openIndex(sColumnName); } // next } // fi } // -------------------------------------------------------------------------- protected void finalize() { try { close(); } catch (StorageException ignore) {} } // -------------------------------------------------------------------------- private void abort() throws StorageException { if (DebugFile.trace) { DebugFile.writeln("Begin DBTable.abort()"); DebugFile.incIdent(); } if (oTrn!=null) { try { oTrn.abort(); oTrn = null; } catch (DatabaseException dbe) { throw new StorageException(dbe.getMessage(),dbe); } } // fi if (DebugFile.trace) { DebugFile.decIdent(); DebugFile.writeln("End DBTable.abort()"); } } // close // -------------------------------------------------------------------------- public void close() throws StorageException { if (DebugFile.trace) { DebugFile.writeln("Begin DBTable.close()"); DebugFile.incIdent(); } if (oTrn!=null) { try { oTrn.commitNoSync(); oTrn = null; } catch (DatabaseException dbe) { throw new StorageException(dbe.getMessage(),dbe); } } // fi try { for (String sColumnName : oInd.keySet()) { oInd.get(sColumnName).close(); } // next oRep.ungetConnection(sCnm); if (oFdb!=null) oFdb.close(); oFdb = null; if (oPdb!=null) oPdb.close(); oPdb = null; } catch (DatabaseException dbe) { if (DebugFile.trace) DebugFile.decIdent(); throw new StorageException(dbe.getMessage(), dbe); } if (DebugFile.trace) { DebugFile.decIdent(); DebugFile.writeln("End DBTable.close()"); } } // close // -------------------------------------------------------------------------- public boolean isReadOnly() throws StorageException { try { return oPdb.getConfig().getReadOnly(); } catch (DatabaseException dbe) { throw new StorageException(dbe.getMessage(), dbe); } } // -------------------------------------------------------------------------- public String getName() { return sCnm; } // -------------------------------------------------------------------------- public LinkedList<Column> columns() { LinkedList<Column> oLst; try { if (null==getDataSource().getMetaData()) return null; oLst = getDataSource().getMetaData().getColumns(getName()); } catch (Exception xcpt) { oLst = null; } return oLst; } // -------------------------------------------------------------------------- public Database getDatabase() { return oPdb; } // -------------------------------------------------------------------------- public DataSource getDataSource() { return (DataSource) oRep; } // -------------------------------------------------------------------------- public void delete(AbstractRecord oRec, Transaction oTrans) throws StorageException { if (oRec.getPrimaryKey()==null) throw new StorageException("Tried to delete record with no primary key"); if (DebugFile.trace) { DebugFile.writeln("Begin DBTable.delete("+oRec.getTableName()+"."+oRec.getPrimaryKey()+")"); DebugFile.incIdent(); byte[] aByKey = oRec.getPrimaryKey().getBytes(); StringBuffer oStrKey = new StringBuffer(aByKey.length*3); for (int b=0; b<aByKey.length; b++) oStrKey.append(" "+Integer.toHexString(aByKey[b])); DebugFile.writeln("raw key hex is"+oStrKey.toString()); } try { oPdb.delete(oTrans, new DatabaseEntry(oRec.getPrimaryKey().getBytes())); } catch (DatabaseException dbe) { if (DebugFile.trace) DebugFile.decIdent(); abort(); throw new StorageException(dbe.getMessage(), dbe); } if (DebugFile.trace) { DebugFile.writeln("End DBTable.delete()"); DebugFile.decIdent(); } } // delete // -------------------------------------------------------------------------- public void delete(AbstractRecord oRec) throws StorageException { delete(oRec, oTrn); } // -------------------------------------------------------------------------- public boolean exists(String sKey) throws StorageException { if (DebugFile.trace) { DebugFile.writeln("Begin DBTable.exists("+sKey+" at "+sCnm+"."+sDbk+")"); DebugFile.incIdent(); byte[] aByKey = sKey.getBytes(); StringBuffer oStrKey = new StringBuffer(aByKey.length*3); for (int b=0; b<aByKey.length; b++) oStrKey.append(" "+Integer.toHexString(aByKey[b])); DebugFile.writeln("raw key hex is"+oStrKey.toString()); } boolean bRetVal = false; OperationStatus oOpSt; try { oOpSt = oPdb.get(oTrn, new DatabaseEntry(sKey.getBytes()), new DatabaseEntry(), LockMode.DEFAULT); bRetVal = (OperationStatus.SUCCESS==oOpSt); } catch (DeadlockException dlxc) { abort(); throw new StorageException(dlxc.getMessage(), dlxc); } catch (Exception xcpt) { throw new StorageException(xcpt.getMessage(), xcpt); } if (DebugFile.trace) { if (oOpSt!=OperationStatus.SUCCESS) DebugFile.writeln("get() operation status was "+oOpSt.toString()); DebugFile.decIdent(); DebugFile.writeln("End DBTable.exists() : "+String.valueOf(bRetVal)); } return bRetVal; } // -------------------------------------------------------------------------- public Record load(String sKey) throws StorageException { if (DebugFile.trace) { DebugFile.writeln("Begin DBTable.load("+sKey+")"); DebugFile.incIdent(); byte[] aByKey = sKey.getBytes(); StringBuffer oStrKey = new StringBuffer(aByKey.length*3); for (int b=0; b<aByKey.length; b++) oStrKey.append(" "+Integer.toHexString(aByKey[b])); DebugFile.writeln("raw key hex is"+oStrKey.toString()); } DBEntity oDbEnt = null; try { DatabaseEntry oDbKey = new DatabaseEntry(sKey.getBytes()); DatabaseEntry oDbDat = new DatabaseEntry(); if (OperationStatus.SUCCESS==oPdb.get(oTrn, oDbKey, oDbDat, LockMode.DEFAULT)) { DBEntityBinding oDbeb = new DBEntityBinding(oCtg); oDbEnt = oDbeb.entryToObject(oDbKey,oDbDat); } } catch (DeadlockException dlxc) { if (DebugFile.trace) DebugFile.decIdent(); abort(); throw new StorageException(dlxc.getMessage(), dlxc); } catch (Exception xcpt) { if (DebugFile.trace) DebugFile.decIdent(); throw new StorageException(xcpt.getMessage(), xcpt); } if (DebugFile.trace) { DebugFile.decIdent(); DebugFile.writeln("End DBTable.load()"); } return oDbEnt; } // load // -------------------------------------------------------------------------- public Record load(Object[] aKey) throws StorageException { if (aKey.length!=1) throw new StorageException("Berkeley DB does not allow primary key composed of multiple values"); return load(aKey[0].toString()); } // load // -------------------------------------------------------------------------- public Record newRecord() throws StorageException { return new DBEntity(getName(), columns()); } // load // -------------------------------------------------------------------------- public void store(AbstractRecord oRec, Transaction oTrans) throws StorageException { if (isReadOnly()) throw new StorageException("DBTable.store() table "+getName()+" is in read-only mode"); if (oRec==null) throw new NullPointerException("DBTable.store() Record to be stored is null"); if (DebugFile.trace) { DebugFile.writeln("Begin DBTable.store("+getName()+"."+oRec.getPrimaryKey()+")"); DebugFile.incIdent(); } try { if (oRec.getPrimaryKey()==null) { if (oRec.containsKey("dt_created")) oRec.remove("dt_created"); oRec.put("dt_created", oTsFmt.format(new Date())); } else { if (oRec.containsKey("dt_created")) { if (oRec.get("dt_created")==null) oRec.put("dt_created", oTsFmt.format(new Date())); else if (oRec.get("dt_created").equals("")) oRec.put("dt_created", oTsFmt.format(new Date())); } else { oRec.put("dt_created", oTsFmt.format(new Date())); } if (oRec.containsKey("dt_modified")) oRec.remove("dt_modified"); oRec.put("dt_modified", oTsFmt.format(new Date())); } if (null!=oRec.columns()) { boolean bHasCompoundIndexes = false; if (DebugFile.trace) { String sColNames = "Iterating through"; for (Column c : oRec.columns()) sColNames += " "+c.getName(); DebugFile.writeln(sColNames); } for (Column c : oRec.columns()) { String n = c.getName(); boolean bIsEmptyPk = !oRec.containsKey(n); if (!bIsEmptyPk) bIsEmptyPk = (oRec.get(n).toString().length()==0); Object sDefVal = c.getDefaultValue(); if (sDefVal!=null) { if (bIsEmptyPk) { if (sDefVal.equals("GUID")) { oRec.put(n, createUniqueKey()); } else if (sDefVal.equals("SERIAL")) { String sSerial = String.valueOf(oRep.nextVal(n)); int iPadLen = (c.getType()==Types.BIGINT ? 21 : 11) - sSerial.length(); if (iPadLen>0) { char aPad[] = new char[iPadLen]; Arrays.fill(aPad, '0'); sSerial = new String(aPad) + sSerial; } oRec.put(n, sSerial); } else if (sDefVal.equals("NOW")) { oRec.put(n, oTsFmt.format(new Date())); } else if (sDefVal.toString().indexOf('+')<0) { if (sDefVal.toString().startsWith("$")) oRec.put(n, Gadgets.ASCIIEncode(oRec.getString(sDefVal.toString().substring(1))).replace(' ','_').toLowerCase()); else if (sDefVal.toString().startsWith("'")) oRec.put(n, sDefVal.toString().substring(1, sDefVal.toString().indexOf((char) 39, 1))); else oRec.put(n, sDefVal); } else { String[] aCols = Gadgets.split(sDefVal.toString(),'+'); sDefVal = ""; if (aCols!=null) for (int v=0; v<aCols.length; v++) if (aCols[v].startsWith("$")) sDefVal = sDefVal + Gadgets.ASCIIEncode(oRec.getString(aCols[v].substring(1))).replace(' ','_').toLowerCase(); else if (aCols[v].startsWith("'")) sDefVal = sDefVal + aCols[v].substring(1, aCols[v].indexOf((char) 39, 1)); else sDefVal = sDefVal.toString() + oRec.get(aCols[v]); oRec.put(n, sDefVal); } if (c.isPrimaryKey()) { if (DebugFile.trace) DebugFile.writeln("auto setting default value of "+n+" to "+oRec.get(n)); oRec.setPrimaryKey(oRec.get(n).toString()); } } // fi bHasCompoundIndexes = (sDefVal.toString().indexOf('+')>0); } // fi if (!c.isNullable() && !oRec.containsKey(n)) { if (sDefVal==null) throw new IntegrityViolationException(c, null); else if (sDefVal.toString().indexOf('+')<0) throw new IntegrityViolationException(c, null); } if (oRec.containsKey(n) && c.getForeignKey()!=null) { if (oRec.get(n)!=null) { if (oRec.get(n).toString().length()>0) { if (DebugFile.trace) DebugFile.writeln("Checking "+c.getForeignKey()+"."+c.getName()+" for value "+oRec.get(n)); DBTable oFk = (DBTable) oRep.openTable(c.getForeignKey(), new String []{c.getName()}); boolean bExists; String sNum; if (c.getType()==Types.INTEGER) { sNum = String.valueOf(oRec.getInt(n)); if (sNum.length()>=11) { bExists = oFk.exists(sNum); } else { bExists = oFk.exists(sNum); if (!bExists) { char iPad[] = new char[11-sNum.length()]; Arrays.fill(iPad, '0'); bExists = oFk.exists(new String(iPad) + sNum); if (bExists) oRec.put(n,new String(iPad) + sNum); } // fi (!bExists) } } else if (c.getType()==Types.BIGINT) { sNum = String.valueOf(oRec.getLong(n)); if (sNum.length()>=21) { bExists = oFk.exists(sNum); } else { bExists = oFk.exists(sNum); if (!bExists) { char iPad[] = new char[21-sNum.length()]; Arrays.fill(iPad, '0'); bExists = oFk.exists(new String(iPad) + sNum); if (bExists) oRec.put(n,new String(iPad) + sNum); } // fi (!bExists) } } else { sNum = oRec.getString(n); bExists = oFk.exists(sNum); } oFk.close(); if (!bExists) throw new IntegrityViolationException(c,sNum); } // fi } // fi () } // fi (c.getForeignKey()) if (c.getType()==Types.DATE || c.getType()==Types.TIMESTAMP) { if (oRec.containsKey(n)) { if (oRec.get(n) instanceof Date) { String dt = oTsFmt.format((Date) oRec.get(n)); oRec.remove(n); oRec.put(n,dt); } // fi } // fi } // fi if (c.getType()==Types.BOOLEAN) { if (oRec.containsKey(n)) { if (oRec.get(n) instanceof String) { if (oRec.get(n).equals("true") || oRec.get(n).equals("True") || oRec.get(n).equals("TRUE") || oRec.get(n).equals("yes") || oRec.get(n).equals("Yes") || oRec.get(n).equals("YES") || oRec.get(n).equals("1")) oRec.replace(n, Boolean.TRUE); else if (oRec.get(n).equals("false") || oRec.get(n).equals("False") || oRec.get(n).equals("FALSE") || oRec.get(n).equals("no") || oRec.get(n).equals("No") || oRec.get(n).equals("NO") || oRec.get(n).equals("0")) oRec.replace(n, Boolean.FALSE); } // fi (instanceof String) if (oRec.get(n) instanceof Short) { if (oRec.get(n).equals(new Short((short)1))) oRec.replace(n, Boolean.TRUE); else if (oRec.get(n).equals(new Short((short)0))) oRec.replace(n, Boolean.FALSE); } // fi (instanceof String) if (oRec.get(n) instanceof Integer) { if (oRec.get(n).equals(new Integer(1))) oRec.replace(n, Boolean.TRUE); else if (oRec.get(n).equals(new Integer(0))) oRec.replace(n, Boolean.FALSE); } // fi (instanceof String) if (!(oRec.get(n) instanceof Boolean)) { throw new IntegrityViolationException(c,"Must be of type Boolean but is actually "+oRec.get(n).getClass().getName()); } } } // fi (BOOLEAN) } // next if (DebugFile.trace) { DebugFile.writeln("table "+(bHasCompoundIndexes ? " has " : " has not ")+"compound indexes"); } if (bHasCompoundIndexes) { for (Column c : oRec.columns()) { String n = c.getName(); if (c.getDefaultValue()!=null) { String sDefVal = c.getDefaultValue().toString(); if (sDefVal.indexOf('+')>0) { // boolean bIsEmptyPk = !oRec.containsKey(n); // if (!bIsEmptyPk) bIsEmptyPk = (oRec.get(n).toString().length()==0); // if (bIsEmptyPk) { String[] aIndexColumns = sDefVal.split("\\x2B"); StringBuffer oCompoundIndexValue = new StringBuffer(1000); for (int i=0; i<aIndexColumns.length; i++) { oCompoundIndexValue.append(oRec.get(aIndexColumns[i])); } // next oRec.put(n, oCompoundIndexValue.toString()); if (DebugFile.trace) { DebugFile.writeln("compound index "+n+" value set to \""+oRec.getString(n)+"\""); } // } // fi } // fi (indexOf('+')>0) } // fi (getDefaultValue()) if (!c.check(oRec.get(n))) throw new IntegrityViolationException(c, oRec.get(n)); } // next (c) } else { for (Column c : oRec.columns()) { if (!c.check(oRec.get(c.getName()))) throw new IntegrityViolationException(c, oRec.get(c.getName())); } // next } } // fi if (oRec.getPrimaryKey()==null) throw new IntegrityViolationException("Primary key not set and no default specified at table "+oRec.getTableName()); else if (oRec.getPrimaryKey().length()==0) throw new IntegrityViolationException("Empty string not allowed as primary key at table "+oRec.getTableName()); DBEntity oEnt = (DBEntity) oRec; DBEntityBinding oDbeb = new DBEntityBinding(oCtg); if (DebugFile.trace) { byte[] aByKey = oRec.getPrimaryKey().getBytes(); StringBuffer oStrKey = new StringBuffer(aByKey.length*3); for (int b=0; b<aByKey.length; b++) oStrKey.append(" "+Integer.toHexString(aByKey[b])); DebugFile.writeln("string key his "+oRec.getPrimaryKey()); DebugFile.writeln("raw key hex his"+oStrKey.toString()); } DatabaseEntry oDbKey = new DatabaseEntry(oRec.getPrimaryKey().getBytes()); DatabaseEntry oDbDat = new DatabaseEntry(oDbeb.objectToData(oEnt)); if (DebugFile.trace) DebugFile.writeln("Database.put("+(oTrans==null ? "null" : oTrans)+","+oRec.getPrimaryKey()+","+"[DatabaseEntry])"); oPdb.put(oTrans, oDbKey, oDbDat); } catch (DeadlockException dlxc) { if (DebugFile.trace) { DebugFile.writeln("DeadlockException "+dlxc.getMessage()); try { DebugFile.writeln(StackTraceUtil.getStackTrace(dlxc)); } catch (java.io.IOException ignore) {} DebugFile.decIdent(); } abort(); throw new StorageException(dlxc.getMessage(), dlxc); } catch (Exception xcpt) { if (DebugFile.trace) { DebugFile.writeln(xcpt.getClass().getName()+" "+xcpt.getMessage()); try { DebugFile.writeln(StackTraceUtil.getStackTrace(xcpt)); } catch (java.io.IOException ignore) {} DebugFile.decIdent(); } throw new StorageException(xcpt.getMessage(), xcpt); } if (DebugFile.trace) { DebugFile.decIdent(); DebugFile.writeln("End DBTable.store() : "+oRec.getPrimaryKey()); } } // store // -------------------------------------------------------------------------- public void store(AbstractRecord oRec) throws StorageException { store(oRec, oTrn); } // store // -------------------------------------------------------------------------- public RecordSet fetch(final int iMaxRows, final int iOffset) throws StorageException { DBEntitySet oEst = new DBEntitySet(); DatabaseEntry oDbKey = new DatabaseEntry(); DatabaseEntry oDbDat = new DatabaseEntry(); OperationStatus oOst; Cursor oCur = null; int iFetched = 0; int iAdded = 0; if (DebugFile.trace) { DebugFile.writeln("Begin DBTable.fetch()"); DebugFile.incIdent(); } try { DBEntityBinding oDbeb = new DBEntityBinding(oCtg); oCur = oPdb.openCursor(null,null); oOst = oCur.getFirst(oDbKey, oDbDat, LockMode.DEFAULT); while (oOst == OperationStatus.SUCCESS && iAdded<iMaxRows) { if (++iFetched>iOffset) { oEst.add(oDbeb.entryToObject(oDbKey,oDbDat)); iAdded++; } oOst = oCur.getNext(oDbKey, oDbDat, LockMode.DEFAULT); } // wend oCur.close(); oCur=null; } catch (DeadlockException dlxc) { if (DebugFile.trace) DebugFile.decIdent(); abort(); throw new StorageException(dlxc.getMessage(), dlxc); } catch (Exception xcpt) { if (DebugFile.trace) DebugFile.decIdent(); throw new StorageException(xcpt.getMessage(), xcpt); } finally { try { if (oCur!=null) oCur.close(); } catch (Exception ignore) { } } if (DebugFile.trace) { DebugFile.decIdent(); DebugFile.writeln("End DBTable.fetch()"); } return oEst; } // fetch // -------------------------------------------------------------------------- public RecordSet fetch() throws StorageException { return fetch(2147483647,0); } // -------------------------------------------------------------------------- public RecordSet fetch(final String sIndexColumn, String sIndexValue, final int iMaxRows) throws StorageException { if (DebugFile.trace) { DebugFile.writeln("Begin DBTable.fetch("+sIndexColumn+","+sIndexValue+","+String.valueOf(iMaxRows)+")"); DebugFile.incIdent(); } if (null==sIndexColumn) throw new StorageException("DBTable.fetch() Column name may not be null"); if (!oInd.containsKey(sIndexColumn)) throw new StorageException("DBTable.fetch() Column "+sIndexColumn+" is not a secondary index"); if (null==sIndexValue) sIndexValue = ""; if (iMaxRows<=0) throw new StorageException("Invalid value for max rows parameter "+String.valueOf(iMaxRows)); DBEntitySet oEst = new DBEntitySet(); Cursor oPur = null; SecondaryCursor oCur = null; OperationStatus oOst; try { DBEntityBinding oDbeb = new DBEntityBinding(oCtg); DatabaseEntry oDbDat = new DatabaseEntry(); DatabaseEntry oDbKey = new DatabaseEntry(); if (sIndexValue.equals("%") || sIndexValue.equalsIgnoreCase("IS NOT NULL")) { if (DebugFile.trace) DebugFile.writeln("Database.openCursor(null,null)"); oPur = oPdb.openCursor(null,null); oOst = oPur.getFirst(oDbKey, oDbDat, LockMode.DEFAULT); while (oOst == OperationStatus.SUCCESS) { Record oRec = oDbeb.entryToObject(oDbKey,oDbDat); if (oRec.containsKey(sIndexColumn)) if (oRec.get(sIndexColumn)!=null) oEst.add(oRec); oOst = oPur.getNext(oDbKey, oDbDat, LockMode.DEFAULT); } // wend oPur.close(); oPur = null; } else if (sIndexValue.equalsIgnoreCase("NULL") || sIndexValue.equalsIgnoreCase("IS NULL")) { if (DebugFile.trace) DebugFile.writeln("Database.openCursor(null,null)"); oPur = oPdb.openCursor(null,null); oOst = oPur.getFirst(oDbKey, oDbDat, LockMode.DEFAULT); while (oOst == OperationStatus.SUCCESS) { Record oRec = oDbeb.entryToObject(oDbKey,oDbDat); if (!oRec.containsKey(sIndexColumn)) oEst.add(oRec); else if (oRec.get(sIndexColumn)==null) oEst.add(oRec); oOst = oPur.getNext(oDbKey, oDbDat, LockMode.DEFAULT); } // wend oPur.close(); oPur=null; } else { DBIndex oIdx = oInd.get(sIndexColumn); if (oIdx.isClosed()) openIndex(sIndexColumn); oCur = oIdx.getCursor(oTrn); int r = -1; if (sIndexValue.endsWith("%")) { sIndexValue = sIndexValue.substring(0,sIndexValue.length()-1); oDbKey = new DatabaseEntry(sIndexValue.getBytes()); oOst = oCur.getSearchKeyRange(oDbKey, oDbDat, LockMode.DEFAULT); while (oOst==OperationStatus.SUCCESS && iMaxRows>++r) { Record oRec = oDbeb.entryToObject(oDbKey,oDbDat); if (oRec.containsKey(sIndexColumn)) { if (oRec.getString(sIndexColumn,"").startsWith(sIndexValue)) { oEst.add(oRec); oOst = oCur.getNext(oDbKey, oDbDat, LockMode.DEFAULT); } else { oOst=OperationStatus.NOTFOUND; } } else { oOst=OperationStatus.KEYEMPTY; } } // wend } else { oDbKey = new DatabaseEntry(sIndexValue.getBytes()); oOst = oCur.getSearchKey(oDbKey, oDbDat, LockMode.DEFAULT); while (oOst==OperationStatus.SUCCESS && iMaxRows>++r) { oEst.add(oDbeb.entryToObject(oDbKey,oDbDat)); oOst = oCur.getNextDup(oDbKey, oDbDat, LockMode.DEFAULT); } // wend } // fi oCur.close(); oCur=null; } // fi } catch (DeadlockException dlxc) { if (DebugFile.trace) { DebugFile.writeln(dlxc.getClass().getName()+" "+dlxc.getMessage()); try { DebugFile.writeln(StackTraceUtil.getStackTrace(dlxc)); } catch (Exception ignore) { } DebugFile.decIdent(); } abort(); throw new StorageException(dlxc.getMessage(), dlxc); } catch (Exception xcpt) { if (DebugFile.trace) { DebugFile.writeln(xcpt.getClass().getName()+" "+xcpt.getMessage()); try { DebugFile.writeln(StackTraceUtil.getStackTrace(xcpt)); } catch (Exception ignore) { } DebugFile.decIdent(); } throw new StorageException(xcpt.getMessage(), xcpt); } finally { try { if (oPur!=null) oPur.close(); } catch (Exception ignore) { } try { if (oCur!=null) oCur.close(); } catch (Exception ignore) { } } if (DebugFile.trace) { DebugFile.decIdent(); DebugFile.writeln("End DBTable.fetch() : " + String.valueOf(oEst.size())); } return oEst; } // fetch // -------------------------------------------------------------------------- public RecordSet fetch(final String sIndexColumn, String sIndexValueMin, String sIndexValueMax) throws StorageException { if (DebugFile.trace) { DebugFile.writeln("Begin DBTable.fetch("+sIndexColumn+","+sIndexValueMin+","+sIndexValueMax+")"); DebugFile.incIdent(); } if (null==sIndexColumn) throw new StorageException("DBTable.fetch() Column name may not be null"); if (null==sIndexValueMin) throw new StorageException("DBTable.fetch() Index minimum value may not be null"); if (null==sIndexValueMax) throw new StorageException("DBTable.fetch() Index maximum value may not be null"); if (!oInd.containsKey(sIndexColumn)) throw new StorageException("DBTable.fetch() Column "+sIndexColumn+" is not indexed"); DBEntitySet oEst = new DBEntitySet(); Cursor oPur = null; SecondaryCursor oCur = null; OperationStatus oOst; try { Comparable oValue; DBEntity oDbEnt; DBEntityBinding oDbeb = new DBEntityBinding(oCtg); DatabaseEntry oDbDat = new DatabaseEntry(); DatabaseEntry oDbKey = new DatabaseEntry();; boolean bMinExists; DBIndex oIdx = oInd.get(sIndexColumn); if (oIdx.isClosed()) openIndex(sIndexColumn); oCur = oIdx.getCursor(oTrn); if (DebugFile.trace) DebugFile.writeln("got SecondaryCursor for "+sIndexColumn); oDbKey = new DatabaseEntry(sIndexValueMin.getBytes()); oOst = oCur.getSearchKey(oDbKey, oDbDat, LockMode.DEFAULT); bMinExists = (oOst==OperationStatus.SUCCESS); oCur.close(); oCur = null; if (DebugFile.trace) DebugFile.writeln(sIndexColumn+" has "+(bMinExists ? "" : "not")+" a minimum value"); if (bMinExists) { oCur = oIdx.getCursor(oTrn); oDbKey = new DatabaseEntry(sIndexValueMin.getBytes()); oOst = oCur.getSearchKey(oDbKey, oDbDat, LockMode.DEFAULT); while (oOst==OperationStatus.SUCCESS) { oDbEnt = oDbeb.entryToObject(oDbKey,oDbDat); oValue = (Comparable)oDbEnt.get(sIndexColumn); if (oValue!=null) { if (oValue.compareTo(sIndexValueMax)>0) break; oEst.add(oDbEnt); } oOst = oCur.getNext(oDbKey, oDbDat, LockMode.DEFAULT); } // wend oCur.close(); oCur=null; } else { oPur = oPdb.openCursor(null,null); oOst = oPur.getFirst(oDbKey, oDbDat, LockMode.DEFAULT); while (oOst==OperationStatus.SUCCESS) { oDbEnt = oDbeb.entryToObject(oDbKey,oDbDat); oValue = (Comparable)oDbEnt.get(sIndexColumn); if (oValue!=null) { if (oValue.compareTo(sIndexValueMax)>0) break; oEst.add(oDbEnt); } oOst = oPur.getNext(oDbKey, oDbDat, LockMode.DEFAULT); } // wend oPur.close(); oPur=null; } } catch (DeadlockException dlxc) { if (DebugFile.trace) { DebugFile.writeln(dlxc.getClass().getName()+" "+dlxc.getMessage()); try { DebugFile.writeln(StackTraceUtil.getStackTrace(dlxc)); } catch (Exception ignore) { } DebugFile.decIdent(); } abort(); throw new StorageException(dlxc.getMessage(), dlxc); } catch (Exception xcpt) { if (DebugFile.trace) { DebugFile.writeln(xcpt.getClass().getName()+" "+xcpt.getMessage()); try { DebugFile.writeln(StackTraceUtil.getStackTrace(xcpt)); } catch (Exception ignore) { } DebugFile.decIdent(); } throw new StorageException(xcpt.getMessage(), xcpt); } finally { try { if (oPur!=null) oPur.close(); } catch (Exception ignore) { } try { if (oCur!=null) oCur.close(); } catch (Exception ignore) { } } if (DebugFile.trace) { DebugFile.decIdent(); DebugFile.writeln("End DBTable.fetch() " + String.valueOf(oEst.size())); } return oEst; } // fetch // -------------------------------------------------------------------------- public RecordSet fetch(final String sIndexColumn, Date dtIndexValueMin, Date dtIndexValueMax) throws StorageException { if (dtIndexValueMax==null) return fetch (sIndexColumn, oTsFmt.format(dtIndexValueMin), 2147483647); else if (dtIndexValueMin==null) return fetch (sIndexColumn, "0000-00-00 00:00:00", oTsFmt.format(dtIndexValueMax)); else return fetch (sIndexColumn, oTsFmt.format(dtIndexValueMin), oTsFmt.format(dtIndexValueMax)); } // -------------------------------------------------------------------------- public RecordSet fetch(NameValuePair[] aIndexedValues, final int iMaxRows) throws StorageException { if (null==aIndexedValues) throw new StorageException("DBTable.fetch() Column names may not be null"); final int nValues = aIndexedValues.length; if (1==nValues) { return fetch (aIndexedValues[0].getName(),aIndexedValues[0].getValue(), iMaxRows); } else { if (DebugFile.trace) { String sPairs = ""; for (int nv=0; nv<nValues; nv++) { sPairs+=(nv==0 ? "" : ",")+aIndexedValues[nv].getName()+":"+aIndexedValues[nv].getValue(); if (!oInd.containsKey(aIndexedValues[nv].getName())) throw new StorageException("DBTable.fetch() Column "+aIndexedValues[nv].getName()+" is not indexed"); if (aIndexedValues[nv].getValue()==null) throw new StorageException("DBTable.fetch() Column "+aIndexedValues[nv].getName()+" may not be null"); if (aIndexedValues[nv].getValue().indexOf('%')>=0) throw new StorageException("DBTable.fetch() "+aIndexedValues[nv].getName()+" % wildcards are not allowed in join cursors"); if (aIndexedValues[nv].getValue().equalsIgnoreCase("null") || aIndexedValues[nv].getValue().equalsIgnoreCase("is null") || aIndexedValues[nv].getValue().equalsIgnoreCase("is not null")) throw new StorageException("DBTable.fetch() "+aIndexedValues[nv].getName()+" IS [NOT] NULL conditional is not allowed in join cursors"); } // next DebugFile.writeln("Begin DBTable.fetch({"+sPairs+"},"+String.valueOf(iMaxRows)+")"); DebugFile.incIdent(); } if (iMaxRows<=0) throw new StorageException("Invalid value for max rows parameter "+String.valueOf(iMaxRows)); DBEntityBinding oDbeb = new DBEntityBinding(oCtg); DatabaseEntry oDbDat = new DatabaseEntry(); DatabaseEntry oDbKey = new DatabaseEntry(); DBEntitySet oEst = new DBEntitySet(); JoinCursor oJur = null; OperationStatus[] aOst = new OperationStatus[nValues]; DBIndex[] aIdxs = new DBIndex[nValues]; SecondaryCursor[] aCurs = new SecondaryCursor[aIndexedValues.length]; try { for (int sc=0; sc<nValues; sc++) { aIdxs[sc] = oInd.get(aIndexedValues[sc].getName()); if (aIdxs[sc].isClosed()) openIndex(aIndexedValues[sc].getName()); aCurs[sc] = aIdxs[sc].getCursor(oTrn); aOst[sc] = aCurs[sc].getSearchKey(new DatabaseEntry(aIndexedValues[sc].getValue().getBytes()), new DatabaseEntry(), LockMode.DEFAULT); } // next oJur = oPdb.join(aCurs, JoinConfig.DEFAULT); while (oJur.getNext(oDbKey, oDbDat, LockMode.DEFAULT) == OperationStatus.SUCCESS) { Record oRec = oDbeb.entryToObject(oDbKey,oDbDat); oEst.add(oRec); } // wend oJur.close(); oJur=null; for (int sc=nValues-1; sc>=0; sc--) { aCurs[sc].close(); aIdxs[sc].close(); } } catch (DeadlockException dlxc) { if (DebugFile.trace) { DebugFile.writeln(dlxc.getClass().getName()+" "+dlxc.getMessage()); try { DebugFile.writeln(StackTraceUtil.getStackTrace(dlxc)); } catch (Exception ignore) { } DebugFile.decIdent(); } abort(); throw new StorageException(dlxc.getMessage(), dlxc); } catch (Exception xcpt) { if (DebugFile.trace) { DebugFile.writeln(xcpt.getClass().getName()+" "+xcpt.getMessage()); try { DebugFile.writeln(StackTraceUtil.getStackTrace(xcpt)); } catch (Exception ignore) { } DebugFile.decIdent(); } throw new StorageException(xcpt.getMessage(), xcpt); } finally { try { if (oJur!=null) oJur.close(); } catch (Exception ignore) { } for (int sc=nValues-1; sc>=0; sc--) { try { if (aCurs[sc]!=null) aCurs[sc].close(); } catch (Exception ignore) { } try { if (aIdxs[sc]!=null) aIdxs[sc].close(); } catch (Exception ignore) { } } // next } if (DebugFile.trace) { DebugFile.decIdent(); DebugFile.writeln("End DBTable.fetch() : " + String.valueOf(oEst.size())); } return oEst; } // fi } // fetch // -------------------------------------------------------------------------- public RecordSet last(final String sOrderByColumn, final int iRows, final int iOffset) throws StorageException { DBEntitySet oEst = new DBEntitySet(); DBEntityBinding oDbeb = new DBEntityBinding(oCtg); DatabaseEntry oDbDat = new DatabaseEntry(); DatabaseEntry oDbKey = new DatabaseEntry();; OperationStatus oOst; Cursor oPur = null; SecondaryCursor oCur = null; int iFetched = 0; int iAdded = 0; try { if (sOrderByColumn==null) { oPur = oPdb.openCursor(null,null); oOst = oPur.getLast(oDbKey, oDbDat, LockMode.DEFAULT); while (oOst==OperationStatus.SUCCESS && iAdded<iRows) { if (++iFetched>iOffset) { oEst.add(oDbeb.entryToObject(oDbKey,oDbDat)); iAdded++; } oOst = oPur.getPrev(oDbKey, oDbDat, LockMode.DEFAULT); } // wend oPur.close(); oPur=null; } else { DBIndex oIdx = oInd.get(sOrderByColumn); if (oIdx.isClosed()) openIndex(sOrderByColumn); oCur = oIdx.getCursor(oTrn); oOst = oCur.getLast(oDbKey, oDbDat, LockMode.DEFAULT); while (oOst==OperationStatus.SUCCESS && iAdded<iRows) { if (++iFetched>iOffset) { oEst.add(oDbeb.entryToObject(oDbKey,oDbDat)); iAdded++; } oOst = oCur.getPrevDup(oDbKey, oDbDat, LockMode.DEFAULT); } // wend oCur.close(); oCur=null; } } catch (DeadlockException dlxc) { abort(); throw new StorageException(dlxc.getMessage(), dlxc); } catch (Exception xcpt) { throw new StorageException(xcpt.getMessage(), xcpt); } finally { try { if (oCur!=null) oCur.close(); } catch (Exception ignore) { } try { if (oPur!=null) oPur.close(); } catch (Exception ignore) { } } return oEst; } // last // -------------------------------------------------------------------------- public RecordSet fetch(final String sIndexColumn, final String sIndexValue) throws StorageException { return fetch (sIndexColumn, sIndexValue, 2147483647); } // -------------------------------------------------------------------------- public void openIndex(String sColumnName) throws StorageException { if (DebugFile.trace) DebugFile.writeln("openIndex("+sColumnName+")"); DBIndex oIdx = oInd.get(sColumnName); SecondaryConfig oSec = new SecondaryConfig(); oSec.setAllowCreate(true); oSec.setAllowPopulate(true); oSec.setSortedDuplicates(true); oSec.setType(DatabaseType.BTREE); oSec.setReadOnly(isReadOnly()); if (DebugFile.trace) DebugFile.writeln("relation type is "+oIdx.getRelationType()); if (oIdx.getRelationType().equalsIgnoreCase("one-to-one") || oIdx.getRelationType().equalsIgnoreCase("many-to-one")) { oSec.setKeyCreator(oRep.getKeyCreator(oIdx.getName())); } else if (oIdx.getRelationType().equalsIgnoreCase("one-to-many") || oIdx.getRelationType().equalsIgnoreCase("many-to-many")) { oSec.setMultiKeyCreator(oRep.getMultiKeyCreator(oIdx.getName())); } else { throw new StorageException("Invalid relationship "+oIdx.getRelationType()); } try { final String sSecDbPath = oRep.getPath()+oPdb.getDatabaseName()+"."+oIdx.getName()+".db"; final String sSecIdxName= oPdb.getDatabaseName()+"_"+oIdx.getName(); if (DebugFile.trace) DebugFile.writeln("DBIndex.open(openSecondaryDatabase("+oTrn+","+sSecDbPath+","+sSecIdxName+","+oPdb+","+oSec+","+String.valueOf(isReadOnly())+"))"); oIdx.open(oRep.getEnvironment().openSecondaryDatabase(oTrn, sSecDbPath, sSecIdxName, oPdb, oSec)); } catch (DeadlockException dle) { if (DebugFile.trace) DebugFile.writeln("DeadlockException "+String.valueOf(dle.getErrno())+" whilst opening secondary database "+dle.getMessage()); abort(); throw new StorageException(dle.getMessage(), dle); } catch (DatabaseException dbe) { if (DebugFile.trace) DebugFile.writeln("DatabaseException "+String.valueOf(dbe.getErrno())+" whilst opening secondary database "+dbe.getMessage()); throw new StorageException(dbe.getMessage(), dbe); } catch (FileNotFoundException fnf) { if (DebugFile.trace) DebugFile.writeln("FileNotFoundException "+fnf.getMessage()); throw new StorageException(fnf.getMessage(), fnf); } } // -------------------------------------------------------------------------- public void delete(final String sIndexColumn, final String sIndexValue) throws StorageException { Transaction oTrd = null; SecondaryDatabase oSdb = null; SecondaryCursor oCur = null; SecondaryConfig oSec = new SecondaryConfig(); oSec.setAllowCreate(false); oSec.setAllowPopulate(false); oSec.setType(DatabaseType.BTREE); oSec.setReadOnly(true); try { String sIndexName; if (sIndexColumn.startsWith("one-to-one")) { sIndexName = sIndexColumn.substring(10).trim(); oSec.setKeyCreator(oRep.getKeyCreator(sIndexName)); } else if (sIndexColumn.startsWith("many-to-one")) { sIndexName = sIndexColumn.substring(11).trim(); oSec.setKeyCreator(oRep.getKeyCreator(sIndexName)); } else if (sIndexColumn.startsWith("one-to-many")) { sIndexName = sIndexColumn.substring(11).trim(); oSec.setMultiKeyCreator(oRep.getMultiKeyCreator(sIndexName)); } else if (sIndexColumn.startsWith("many-to-many")) { sIndexName = sIndexColumn.substring(12).trim(); oSec.setMultiKeyCreator(oRep.getMultiKeyCreator(sIndexName)); } else /* many-to-one assumed by default */ { sIndexName = sIndexColumn.trim(); oSec.setKeyCreator(oRep.getKeyCreator(sIndexName)); } oSdb = oRep.getEnvironment().openSecondaryDatabase(oTrn, oRep.getPath()+oPdb.getDatabaseName()+"."+sIndexName+".db", oPdb.getDatabaseName()+"_"+sIndexName, oPdb, oSec); DBEntityBinding oDbeb = new DBEntityBinding(oCtg); DatabaseEntry oDbKey = new DatabaseEntry(sIndexValue.getBytes()); DatabaseEntry oDbDat = new DatabaseEntry(); oCur = oSdb.openSecondaryCursor(oTrn,null); ArrayList<String> aKeys = new ArrayList<String>(1000); OperationStatus oOst = oCur.getSearchKey(oDbKey, oDbDat, LockMode.DEFAULT); while (oOst == OperationStatus.SUCCESS) { aKeys.add(oDbeb.objectToKey(oDbeb.entryToObject(oDbKey,oDbDat))); oOst = oCur.getNextDup(oDbKey, oDbDat, LockMode.DEFAULT); } // wend oCur.close(); oCur=null; oSdb.close(); oSdb=null; oTrd = oRep.isTransactional() ? oRep.getEnvironment().beginTransaction(oTrn,null) : null; for (String k : aKeys) { delete(k,oTrd); } oTrd.commit(); oTrd=null; } catch (DeadlockException dlxc) { try { if (oTrd!=null) oTrd.abort(); } catch (DatabaseException e) { } abort(); throw new StorageException(dlxc.getMessage(), dlxc); } catch (Exception xcpt) { try { if (oTrd!=null) oTrd.abort(); } catch (DatabaseException e) { } throw new StorageException(xcpt.getMessage(), xcpt); } finally { try { if (oTrd!=null) oTrd.discard(); } catch (Exception ignore) { } try { if (oCur!=null) oCur.close(); } catch (Exception ignore) { } try { if (oSdb!=null) oSdb.close(); } catch (Exception ignore) { } } } // delete // -------------------------------------------------------------------------- public void delete(final String sKeyValue, Transaction oTrans) throws StorageException { try { oPdb.delete(oTrans, new DatabaseEntry(sKeyValue.getBytes())); } catch (Exception xcpt) { throw new StorageException(xcpt.getMessage(), xcpt); } } // delete // -------------------------------------------------------------------------- public void delete(final String sKeyValue) throws StorageException { delete(sKeyValue, oTrn); } // delete // -------------------------------------------------------------------------- public void dropIndex(final String sIndexColumn) throws StorageException { try { if (oInd.containsKey(sIndexColumn)) { DBIndex oIdx = oInd.get(sIndexColumn); oIdx.close(); oInd.remove(sIndexColumn); Database.remove(oRep.getPath()+oPdb.getDatabaseName()+"."+oIdx.getName()+".db", oPdb.getDatabaseName()+"_"+oIdx.getName(), null); } else { throw new StorageException("Index not found "+sIndexColumn); } } catch (DatabaseException dbe) { throw new StorageException(dbe.getMessage(), dbe); } catch (FileNotFoundException fnf) { throw new StorageException(fnf.getMessage(), fnf); } } // dropIndex // -------------------------------------------------------------------------- public void truncate() throws StorageException { if (isReadOnly()) throw new StorageException("DBTable.truncate() table "+getName()+" is in read-only mode"); try { oPdb.truncate(oTrn,false); } catch (DeadlockException dlxc) { abort(); throw new StorageException(dlxc.getMessage(), dlxc); } catch (Exception xcpt) { throw new StorageException(xcpt.getMessage(), xcpt); } } // truncate // -------------------------------------------------------------------------- public static String createUniqueKey() { int iRnd; long lSeed = new Date().getTime(); Random oRnd = new Random(lSeed); String sHex; StringBuffer sUUID = new StringBuffer(32); byte[] localIPAddr = new byte[4]; try { // 8 characters Code IP address of this machine localIPAddr = InetAddress.getLocalHost().getAddress(); sUUID.append(byteToStr[((int) localIPAddr[0]) & 255]); sUUID.append(byteToStr[((int) localIPAddr[1]) & 255]); sUUID.append(byteToStr[((int) localIPAddr[2]) & 255]); sUUID.append(byteToStr[((int) localIPAddr[3]) & 255]); } catch (UnknownHostException e) { // Use localhost by default sUUID.append("7F000000"); } // Append a seed value based on current system date sUUID.append(Long.toHexString(lSeed)); // 6 characters - an incremental sequence sUUID.append(Integer.toHexString(iSequence++)); if (iSequence>16777000) iSequence=1048576; do { iRnd = oRnd.nextInt(); if (iRnd>0) iRnd = -iRnd; sHex = Integer.toHexString(iRnd); } while (0==iRnd); // Finally append a random number sUUID.append(sHex); return sUUID.substring(0, 32); } // generateUUID() private static int iSequence = 1048576; private static String[] byteToStr = { "00","01","02","03","04","05","06","07","08","09","0a","0b","0c","0d","0e","0f", "10","11","12","13","14","15","16","17","18","19","1a","1b","1c","1d","1e","1f", "20","21","22","23","24","25","26","27","28","29","2a","2b","2c","2d","2e","2f", "30","31","32","33","34","35","36","37","38","39","3a","3b","3c","3d","3e","3f", "40","41","42","43","44","45","46","47","48","49","4a","4b","4c","4d","4e","4f", "50","51","52","53","54","55","56","57","58","59","5a","5b","5c","5d","5e","5f", "60","61","62","63","64","65","66","67","68","69","6a","6b","6c","6d","6e","6f", "70","71","72","73","74","75","76","77","78","79","7a","7b","7c","7d","7e","7f", "80","81","82","83","84","85","86","87","88","89","8a","8b","8c","8d","8e","8f", "90","91","92","93","94","95","96","97","98","99","9a","9b","9c","9d","9e","9f", "a0","a1","a2","a3","a4","a5","a6","a7","a8","a9","aa","ab","ac","ad","ae","af", "b0","b1","b2","b3","b4","b5","b6","b7","b8","b9","ba","bb","bc","bd","be","bf", "c0","c1","c2","c3","c4","c5","c6","c7","c8","c9","ca","cb","cc","cd","ce","cf", "d0","d1","d2","d3","d4","d5","d6","d7","d8","d9","da","db","dc","dd","de","df", "e0","e1","e2","e3","e4","e5","e6","e7","e8","e9","ea","eb","ec","ed","ee","ef", "f0","f1","f2","f3","f4","f5","f6","f7","f8","f9","fa","fb","fc","fd","fe","ff" }; }