![JAR search and dependency download from the Maven repository](/logo.png)
org.jpos.space.JDBMSpace Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jpos Show documentation
Show all versions of jpos Show documentation
jPOS is an ISO-8583 based financial transaction
library/framework that can be customized and
extended in order to implement financial interchanges.
/*
* jPOS Project [http://jpos.org]
* Copyright (C) 2000-2023 jPOS Software SRL
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
package org.jpos.space;
import jdbm.RecordManager;
import jdbm.RecordManagerFactory;
import jdbm.RecordManagerOptions;
import jdbm.helper.FastIterator;
import jdbm.helper.Serializer;
import jdbm.htree.HTree;
import org.jpos.util.DefaultTimer;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.time.Duration;
import java.time.Instant;
import java.util.*;
/**
* JDBM based persistent space implementation
*
* @author Alejandro Revilla
* @author Kris Leite
* @version $Revision$ $Date$
* @since 1.4.7
*/
@SuppressWarnings("unchecked")
public class JDBMSpace extends TimerTask implements Space {
protected HTree htree;
protected RecordManager recman;
protected static final Serializer refSerializer = new Ref ();
protected static final Map spaceRegistrar = new HashMap ();
protected boolean autoCommit = true;
protected String name;
public static final long GCDELAY = 5*60*1000;
private static final long NRD_RESOLUTION = 500L;
/**
* protected constructor.
* @param name Space Name
* @param filename underlying JDBM filename
*/
protected JDBMSpace (String name, String filename) {
super();
this.name = name;
try {
Properties props = new Properties();
props.put (RecordManagerOptions.CACHE_SIZE, "512");
recman = RecordManagerFactory.createRecordManager (filename, props);
long recid = recman.getNamedObject ("space");
if (recid != 0) {
htree = HTree.load (recman, recid);
} else {
htree = HTree.createInstance (recman);
recman.setNamedObject ("space", htree.getRecid());
}
recman.commit ();
} catch (IOException e) {
throw new SpaceError (e);
}
DefaultTimer.getTimer().schedule (this, GCDELAY, GCDELAY);
}
/**
* @return reference to default JDBMSpace
*/
public static JDBMSpace getSpace() {
return getSpace ("space");
}
/**
* creates a named JDBMSpace
* (filename used for storage is the same as the given name)
* @param name the Space name
* @return reference to named JDBMSpace
*/
public static JDBMSpace getSpace(String name) {
return getSpace(name, name);
}
/**
* creates a named JDBMSpace
* @param name the Space name
* @param filename the storage file name
* @return reference to named JDBMSpace
*/
public synchronized static JDBMSpace
getSpace (String name, String filename)
{
JDBMSpace sp = (JDBMSpace) spaceRegistrar.get (name);
if (sp == null) {
sp = new JDBMSpace (name, filename);
spaceRegistrar.put (name, sp);
}
return sp;
}
/**
* Use with utmost care and at your own risk.
*
* If you are to perform several operations on the space you
* should synchronize on the space, i.e:
*
* synchronized (sp) {
* sp.setAutoCommit (false);
* sp.out (..., ...)
* sp.out (..., ...)
* ...
* ...
* sp.inp (...);
* sp.commit (); // or sp.rollback ();
* sp.setAutoCommit (true);
* }
*
* @param b true or false
*/
public void setAutoCommit (boolean b) {
this.autoCommit = b;
}
/**
* force commit
*/
public void commit () {
try {
recman.commit ();
this.notifyAll ();
} catch (IOException e) {
throw new SpaceError (e);
}
}
/**
* force rollback
*/
public void rollback () {
try {
recman.rollback ();
} catch (IOException e) {
throw new SpaceError (e);
}
}
/**
* close this space - use with care
*/
public void close () {
synchronized (JDBMSpace.class) {
spaceRegistrar.remove (name);
}
synchronized (this) {
try {
recman.close ();
recman = null;
} catch (IOException e) {
throw new SpaceError (e);
}
}
}
/**
* Write a new entry into the Space
* @param key Entry's key
* @param value Object value
*/
public void out (K key, V value) {
out (key, value, -1);
}
/**
* Write a new entry into the Space
* The entry will timeout after the specified period
* @param key Entry's key
* @param value Object value
* @param timeout entry timeout in millis
*/
public void out (K key, V value, long timeout) {
if (key == null || value == null)
throw new NullPointerException ("key=" + key + ", value=" + value);
try {
synchronized (this) {
long recid = recman.insert (value);
long expiration = timeout == -1 ? Long.MAX_VALUE :
Instant.now().toEpochMilli() + timeout;
Ref dataRef = new Ref (recid, expiration);
long dataRefRecId = recman.insert (dataRef, refSerializer);
Head head = (Head) htree.get (key);
if (head == null) {
head = new Head ();
head.first = dataRefRecId;
head.last = dataRefRecId;
head.count = 1;
} else {
long previousLast = head.last;
Ref lastRef =
(Ref) recman.fetch (previousLast, refSerializer);
lastRef.next = dataRefRecId;
head.last = dataRefRecId;
head.count++;
recman.update (previousLast, lastRef, refSerializer);
}
htree.put (key, head);
if (autoCommit) {
recman.commit ();
this.notifyAll ();
}
}
} catch (IOException e) {
throw new SpaceError (e);
}
}
public void push (K key, V value) {
push (key, value, -1);
}
/**
* Write a new entry into the Space at the head of a queue
* The entry will timeout after the specified period
* @param key Entry's key
* @param value Object value
* @param timeout entry timeout in millis
*/
public void push (Object key, Object value, long timeout) {
if (key == null || value == null)
throw new NullPointerException ("key=" + key + ", value=" + value);
try {
synchronized (this) {
long recid = recman.insert (value);
long expiration = timeout == -1 ? Long.MAX_VALUE :
Instant.now().toEpochMilli() + timeout;
Ref dataRef = new Ref (recid, expiration);
Head head = (Head) htree.get (key);
if (head == null) {
head = new Head ();
head.first = head.last = recman.insert (dataRef, refSerializer);
} else {
dataRef.next = head.first;
head.first = recman.insert (dataRef, refSerializer);
}
head.count++;
htree.put (key, head);
if (autoCommit) {
recman.commit ();
this.notifyAll ();
}
}
} catch (IOException e) {
throw new SpaceError (e);
}
}
/**
* Read probe reads an entry from the space if one exists,
* return null otherwise.
* @param key Entry's key
* @return value or null
*/
public synchronized V rdp (Object key) {
try {
if (key instanceof Template)
return (V) getObject ((Template) key, false);
Object obj = null;
Ref ref = getFirst (key, false);
if (ref != null)
obj = recman.fetch (ref.recid);
if (autoCommit)
recman.commit ();
return (V) obj;
} catch (IOException e) {
throw new SpaceError (e);
}
}
/**
* In probe takes an entry from the space if one exists,
* return null otherwise.
* @param key Entry's key
* @return value or null
*/
public synchronized V inp (Object key) {
try {
if (key instanceof Template)
return (V) getObject ((Template) key, true);
Object obj = null;
Ref ref = getFirst (key, true);
if (ref != null) {
obj = recman.fetch (ref.recid);
recman.delete (ref.recid);
}
if (autoCommit)
recman.commit ();
return (V) obj;
} catch (IOException e) {
throw new SpaceError (e);
}
}
public synchronized V in (Object key) {
Object obj;
while ((obj = inp (key)) == null) {
try {
this.wait ();
} catch (InterruptedException ignored) { }
}
return (V) obj;
}
/**
* Take an entry from the space, waiting forever until one exists.
* @param key Entry's key
* @return value
*/
public synchronized V in (Object key, long timeout) {
Object obj;
Instant now = Instant.now();
long duration;
while ((obj = inp (key)) == null &&
(duration = Duration.between(now, Instant.now()).toMillis()) < timeout)
{
try {
this.wait (timeout - duration);
} catch (InterruptedException ignored) { }
}
return (V) obj;
}
/**
* Read an entry from the space, waiting forever until one exists.
* @param key Entry's key
* @return value
*/
public synchronized V rd (Object key) {
Object obj;
while ((obj = rdp (key)) == null) {
try {
this.wait ();
} catch (InterruptedException ignored) { }
}
return (V) obj;
}
/**
* Read an entry from the space, waiting a limited amount of time
* until one exists.
* @param key Entry's key
* @param timeout millis to wait
* @return value or null
*/
public synchronized V rd (Object key, long timeout) {
Object obj;
Instant now = Instant.now();
long duration;
while ((obj = rdp (key)) == null &&
(duration = Duration.between(now, Instant.now()).toMillis()) < timeout)
{
try {
this.wait (timeout - duration);
} catch (InterruptedException ignored) { }
}
return (V) obj;
}
public synchronized void nrd (Object key) {
while (rdp (key) != null) {
try {
this.wait (NRD_RESOLUTION);
} catch (InterruptedException ignored) { }
}
}
public synchronized V nrd (Object key, long timeout) {
Object obj;
Instant now = Instant.now();
long duration;
while ((obj = rdp (key)) != null &&
(duration = Duration.between(now, Instant.now()).toMillis()) < timeout)
{
try {
this.wait (Math.min(NRD_RESOLUTION, timeout - duration));
} catch (InterruptedException ignored) { }
}
return (V) obj;
}
/**
* @param key the Key
* @return aproximately queue size
*/
public long size (Object key) {
try {
Head head = (Head) htree.get (key);
return head != null ? head.count : 0;
} catch (IOException e) {
throw new SpaceError (e);
}
}
public boolean existAny (Object[] keys) {
for (Object key : keys) {
if (rdp(key) != null)
return true;
}
return false;
}
public boolean existAny (Object[] keys, long timeout) {
Instant now = Instant.now();
long duration;
while ((duration = Duration.between(now, Instant.now()).toMillis()) < timeout) {
if (existAny (keys))
return true;
synchronized (this) {
try {
wait (timeout - duration);
} catch (InterruptedException ignored) { }
}
}
return false;
}
public synchronized void put (K key, V value, long timeout) {
while (inp (key) != null)
; // NOPMD
out (key, value, timeout);
}
public synchronized void put (K key, V value) {
while (inp (key) != null)
; // NOPMD
out (key, value);
}
private void purge (Object key) throws IOException {
Head head = (Head) htree.get (key);
Ref previousRef = null;
if (head != null) {
for (long recid = head.first; recid >= 0; ) {
Ref r = (Ref) recman.fetch (recid, refSerializer);
if (r.isExpired ()) {
recman.delete (r.recid);
recman.delete (recid);
head.count--;
if (previousRef == null) {
head.first = r.next;
} else {
previousRef.next = r.next;
recman.update (
head.last, previousRef, refSerializer
);
}
} else {
previousRef = r;
head.last = recid;
}
recid = r.next;
}
if (head.first == -1) {
htree.remove (key);
}
else {
htree.put (key, head);
}
}
}
@Override
public void run () {
try {
gc();
} catch (Exception | SpaceError ex) {
// this happens when e.g. the jdbm file is corrupted
ex.printStackTrace();
}
}
/**
* garbage collector.
* removes expired entries
*/
public void gc () {
final String GCKEY = "GC$" + Integer.toString (hashCode());
final long TIMEOUT = 24 * 3600 * 1000;
Object obj;
try {
synchronized (this) {
// avoid concurrent gc
if (rdp (GCKEY) != null)
return;
((Space)this).out (GCKEY, Boolean.TRUE, TIMEOUT);
}
FastIterator iter = htree.keys ();
try {
while ( (obj = iter.next()) != null) {
((Space)this).out (GCKEY, obj, TIMEOUT);
Thread.yield ();
}
} catch (ConcurrentModificationException e) {
// ignore, we may have better luck on next try
}
while ( (obj = inp (GCKEY)) != null) {
synchronized (this) {
purge (obj);
recman.commit ();
}
Thread.yield ();
}
} catch (IOException e) {
throw new SpaceError (e);
}
}
public String getKeys () {
StringBuilder sb = new StringBuilder();
try {
FastIterator iter = htree.keys ();
Object obj;
while ( (obj = iter.next()) != null) {
if (sb.length() > 0)
sb.append (' ');
sb.append (obj.toString());
}
} catch (IOException e) {
throw new SpaceError (e);
}
return sb.toString();
}
private Ref getFirst (Object key, boolean remove) throws IOException {
Head head = (Head) htree.get (key);
Ref ref = null;
if (head != null) {
long recid;
for (recid = head.first; recid >= 0; ) {
Ref r = (Ref) recman.fetch (recid, refSerializer);
if (r.isExpired ()) {
recman.delete (r.recid);
recman.delete (recid);
recid = r.next;
head.count--;
} else {
ref = r;
if (remove) {
recman.delete (recid);
recid = ref.next;
head.count--;
}
break;
}
}
if (head.first != recid) {
if (recid < 0)
htree.remove (key);
else {
head.first = recid;
htree.put (key, head);
}
}
}
return ref;
}
private void unlinkRef
(long recid, Head head, Ref r, Ref previousRef, long previousRecId)
throws IOException
{
recman.delete (r.recid);
recman.delete (recid);
head.count--;
if (previousRef == null)
head.first = r.next;
else {
previousRef.next = r.next;
recman.update (
previousRecId, previousRef, refSerializer
);
}
}
private Object getObject (Template tmpl, boolean remove)
throws IOException
{
Object obj = null;
Object key = tmpl.getKey();
Head head = (Head) htree.get (key);
Ref previousRef = null;
long previousRecId = 0;
int unlinkCount = 0;
if (head != null) {
for (long recid = head.first; recid >= 0; ) {
Ref r = (Ref) recman.fetch (recid, refSerializer);
if (r.isExpired ()) {
unlinkRef (recid, head, r, previousRef, previousRecId);
unlinkCount++;
} else {
Object o = recman.fetch (r.recid);
if (o != null && tmpl.equals(o)) {
obj = o;
if (remove) {
unlinkRef (
recid, head, r, previousRef, previousRecId
);
unlinkCount++;
}
break;
}
previousRef = r;
previousRecId = recid;
}
recid = r.next;
}
if (unlinkCount > 0) {
if (head.first == -1) {
htree.remove (key);
}
else {
htree.put (key, head);
}
}
}
return obj;
}
static class Head implements Externalizable {
public long first;
public long last;
public long count;
static final long serialVersionUID = 2L;
public Head () {
super ();
first = -1;
last = -1;
}
public void writeExternal (ObjectOutput out) throws IOException {
out.writeLong (first);
out.writeLong (last);
out.writeLong (count);
}
public void readExternal (ObjectInput in) throws IOException {
first = in.readLong ();
last = in.readLong ();
count = in.readLong ();
}
public String toString() {
return getClass().getName()
+ "@" + Integer.toHexString(hashCode())
+ ":[first=" + first
+ ",last=" + last
+ "]";
}
}
static class Ref implements Serializer {
long recid;
long expires;
long next;
static final long serialVersionUID = 1L;
public Ref () {
super();
}
public Ref (long recid, long expires) {
super();
this.recid = recid;
this.expires = expires;
this.next = -1;
}
public boolean isExpired () {
return expires < Instant.now().toEpochMilli();
}
public String toString() {
return getClass().getName()
+ "@" + Integer.toHexString(hashCode())
+ ":[recid=" + recid
+ ",next=" + next
+ ",expired=" + isExpired ()
+ "]";
}
public byte[] serialize (Object obj)
throws IOException
{
Ref d = (Ref) obj;
byte[] buf = new byte [24];
putLong (buf, 0, d.recid);
putLong (buf, 8, d.next);
putLong (buf,16, d.expires);
return buf;
}
public Object deserialize (byte[] serialized)
throws IOException
{
Ref d = new Ref ();
d.recid = getLong (serialized, 0);
d.next = getLong (serialized, 8);
d.expires = getLong (serialized, 16);
return d;
}
}
static void putLong (byte[] b, int off, long val) {
b[off+7] = (byte) val;
b[off+6] = (byte) (val >>> 8);
b[off+5] = (byte) (val >>> 16);
b[off+4] = (byte) (val >>> 24);
b[off+3] = (byte) (val >>> 32);
b[off+2] = (byte) (val >>> 40);
b[off+1] = (byte) (val >>> 48);
b[off] = (byte) (val >>> 56);
}
static long getLong (byte[] b, int off) {
return (b[off+7] & 0xFFL) +
((b[off+6] & 0xFFL) << 8) +
((b[off+5] & 0xFFL) << 16) +
((b[off+4] & 0xFFL) << 24) +
((b[off+3] & 0xFFL) << 32) +
((b[off+2] & 0xFFL) << 40) +
((b[off+1] & 0xFFL) << 48) +
((b[off] & 0xFFL) << 56);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy