All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jpos.space.JDBMSpace Maven / Gradle / Ivy

Go to download

jPOS is an ISO-8583 based financial transaction library/framework that can be customized and extended in order to implement financial interchanges.

There is a newer version: 3.0.0
Show newest version
/*
 * 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