-
Notifications
You must be signed in to change notification settings - Fork 151
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
475 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
[[seqno]] | ||
== SeqNo | ||
|
||
[plantuml, sysconfig, svg] | ||
---- | ||
@startuml | ||
class SeqNo { | ||
String id; | ||
long value; | ||
long lockedBy; | ||
long lockUntil; | ||
} | ||
@enduml | ||
---- | ||
|
||
The `SeqNo` entity footnote:['seqno' table] is a general purpose entity | ||
used by jPOS-EE to store application specific sequencers. | ||
|
||
Typical use case would be terminal-level STANS, Voucher IDs and the like. | ||
|
||
The `SeqNoManager` supports two operating modes: | ||
|
||
- Synchronous | ||
- Asynchronous | ||
|
||
In Synchronous mode, the transaction life-cycle is handled by the caller, e.g.: | ||
|
||
[source,java] | ||
------------- | ||
private long next(String id) { | ||
try (DB db = new DB()) { | ||
SeqNoManager mgr = new SeqNoManager(db); | ||
db.open(); | ||
db.beginTransaction(); | ||
long l = mgr.next(id, 999999L); | ||
db.commit(); | ||
return l; | ||
} | ||
} | ||
------------- | ||
|
||
[NOTE] | ||
====== | ||
In 'sync' mode, a second thread trying to obtain a sequence number will block until the | ||
former is committed. | ||
====== | ||
|
||
In Asynchronous mode, the JDBC connection is released and an explicit call to `SeqNoManager.release` | ||
has to be issued, e.g.: | ||
|
||
[source,java] | ||
------------- | ||
SeqNoManager mgr = new SeqNoManager(new DB()); | ||
long l = mgr.next("sync", id, 60000L, 60000L, 999999L); | ||
// ... do something, e.g.: query remote host | ||
mgr.release("sync", id); | ||
return l; | ||
------------- | ||
|
||
Interesting thing about async mode, is that while one calls a remote host (i.e. using `QueryHost`), | ||
the JDBC connection gets released back to its pool. | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
description = 'jPOS-EE :: SeqNo' | ||
|
||
dependencies { | ||
compile project(':modules:dbsupport') | ||
testCompile project(':modules:db-h2') | ||
// compile project(':modules:db-mysql') | ||
} | ||
|
||
|
||
ext { | ||
testRuntimeDir = "$buildDir/runtime" as File | ||
} | ||
|
||
task jposeeSetup(dependsOn: 'classes', type: JavaExec) { | ||
classpath = sourceSets.test.runtimeClasspath | ||
main = 'org.jpos.q2.install.Install' | ||
args = ["--quiet", "--force", "--outputDir=" + testRuntimeDir] | ||
} | ||
|
||
test { | ||
dependsOn 'jposeeSetup' | ||
scanForTestClasses true | ||
workingDir testRuntimeDir | ||
} | ||
|
||
apply from: "${rootProject.projectDir}/jpos-app.gradle" |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
/* | ||
* jPOS Project [http://jpos.org] | ||
* Copyright (C) 2000-2019 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 <http://www.gnu.org/licenses/>. | ||
*/ | ||
|
||
package org.jpos.ee; | ||
|
||
import java.io.Serializable; | ||
import java.util.Objects; | ||
|
||
import org.hibernate.annotations.CacheConcurrencyStrategy; | ||
|
||
import javax.persistence.*; | ||
|
||
/** | ||
* SeqNo can be used to manage application level sequencers and both synchronous as well as asynchronous locking. | ||
* | ||
* In synchronous mode, a lock is placed on <code>SeqNo.id</code>. | ||
* In asynchronous mode, a unique <code>lockedBy</code> identifier is used, and the entry is considered locked | ||
* until a given timeout, or gets released by calling <code>SeqNoManager.release</code>. | ||
* | ||
* While this entity support both modes (sync/async), applications shouldn't mix them. | ||
* | ||
* @see org.jpos.ee.SeqNoManager | ||
* @since 2.2.6 | ||
*/ | ||
|
||
@Entity | ||
@Table(name = "seqno") | ||
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE) | ||
@SuppressWarnings("unused") | ||
public class SeqNo extends Cloneable implements Serializable { | ||
private static final long serialVersionUID = -1475470843801587648L; | ||
@Id | ||
@Column(length=128) | ||
private String id; | ||
|
||
private long value; | ||
private long lockedBy; | ||
private long lockUntil; | ||
|
||
public SeqNo(String id) { | ||
this.id = id; | ||
} | ||
|
||
public SeqNo() { | ||
super(); | ||
} | ||
|
||
public String getId() { | ||
return this.id; | ||
} | ||
|
||
public void setId(String id) { | ||
this.id = id; | ||
} | ||
|
||
public long getValue() { | ||
return this.value; | ||
} | ||
|
||
public void setValue(long value) { | ||
this.value = value; | ||
} | ||
|
||
/** | ||
* Returns next sequence number. | ||
* | ||
* @param wrapAt greater than wraps back to 1 | ||
* @return next sequence number | ||
*/ | ||
public long next (long wrapAt) { | ||
synchronized (this) { | ||
if (++value > wrapAt) | ||
value = 1; | ||
} | ||
return value; | ||
} | ||
|
||
public long getLockedBy() { | ||
return lockedBy; | ||
} | ||
|
||
public void setLockedBy(long lockedBy) { | ||
this.lockedBy = lockedBy; | ||
} | ||
|
||
public long getLockUntil() { | ||
return lockUntil; | ||
} | ||
|
||
public void setLockUntil(long lockUntil) { | ||
this.lockUntil = lockUntil; | ||
} | ||
|
||
@Override | ||
public boolean equals(Object o) { | ||
if (this == o) return true; | ||
if (o == null || getClass() != o.getClass()) return false; | ||
SeqNo seqNo = (SeqNo) o; | ||
return value == seqNo.value && Objects.equals(id, seqNo.id); | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
return Objects.hash(id, value); | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return "SeqNo{" + | ||
"id='" + id + '\'' + | ||
", value=" + value + | ||
'}'; | ||
} | ||
} |
137 changes: 137 additions & 0 deletions
137
modules/seqno/src/main/java/org/jpos/ee/SeqNoManager.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
/* | ||
* jPOS Project [http://jpos.org] | ||
* Copyright (C) 2000-2019 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 <http://www.gnu.org/licenses/>. | ||
*/ | ||
|
||
package org.jpos.ee; | ||
|
||
import org.hibernate.LockMode; | ||
import org.jpos.iso.ISOUtil; | ||
import javax.persistence.LockTimeoutException; | ||
|
||
/** | ||
* SeqNoManager can be used to manage application level sequencers and both synchronous as well as asynchronous locking. | ||
* | ||
* This Manager can operate in synchronous or asynchronous way. | ||
* In sync mode, the caller needs to provide the transaction life-cycle | ||
* (<code>open, beginTransaction, commit, close</code>). | ||
* | ||
* In async mode the application has to provide a pristine (not opened) DB object | ||
* and the manager takes care of the transaction lifecycle. | ||
* | ||
* @see org.jpos.ee.SeqNoManager | ||
* @since 2.2.6 | ||
*/ | ||
public class SeqNoManager { | ||
private DB db; | ||
|
||
public SeqNoManager(DB db) { | ||
this.db = db; | ||
} | ||
|
||
|
||
/** | ||
* Synchronous 'next' | ||
* @param id sequencer id | ||
* @param wrapAt wrap at value | ||
* @return next sequencer value | ||
*/ | ||
public long next (String id, long wrapAt) { | ||
return getOrCreate(id).next(wrapAt); | ||
} | ||
|
||
/** | ||
* Synchronous 'reset' | ||
* @param id sequencer id | ||
* @param value reset value | ||
*/ | ||
public void reset (String id, long value) { | ||
getOrCreate(id).setValue(value); | ||
} | ||
|
||
/** | ||
* Asynchronous 'next' | ||
* | ||
* @param id sequencer ID | ||
* @param lockedBy unique client identifier (any long, has to be system-wide unique) | ||
* @param lockTimeout once lock is obtained, keep it fo 'lockTimeout' millis (if not 'released' earlier) | ||
* @param timeout time (in millis) to wait for this lock | ||
* @param wrapAt wrap at value | ||
* @return next sequencer value | ||
* @throws LockTimeoutException if lock can't be obtained after 'timeout' has elapsed | ||
*/ | ||
public long next (String id, long lockedBy, long lockTimeout, long timeout, long wrapAt) { | ||
long until = System.currentTimeMillis() + timeout; | ||
if (db.session != null && db.session.isOpen()) | ||
throw new IllegalStateException("DB should not be open"); | ||
while (System.currentTimeMillis() < until) { | ||
try (DB db1 = db) { | ||
db1.open(); | ||
db1.beginTransaction(); | ||
SeqNo seq = getOrCreate(id); | ||
long now = System.currentTimeMillis(); | ||
if (seq.getLockedBy() == 0 || seq.getLockUntil() < now) { | ||
seq.setLockedBy(lockedBy); | ||
seq.setLockUntil(now + lockTimeout); | ||
long stan = seq.next(wrapAt); | ||
db1.commit(); | ||
return stan; | ||
} | ||
db1.commit(); | ||
ISOUtil.sleep(500L); | ||
} | ||
} | ||
throw new LockTimeoutException("Unable to lock " + id + " in less than " + timeout + " millis"); | ||
} | ||
|
||
/** | ||
* Release an async lock | ||
* @param id lock ID | ||
* @param lockedBy unique client identifier | ||
*/ | ||
public void release (String id, long lockedBy) { | ||
if (db.session != null && db.session.isOpen()) | ||
throw new IllegalStateException("DB should not be open"); | ||
try (DB db1 = db) { | ||
db1.open(); | ||
db1.beginTransaction(); | ||
SeqNo seq = getOrCreate(id); | ||
if (seq.getLockedBy() == lockedBy) { | ||
seq.setLockedBy(0L); | ||
db1.commit(); | ||
} | ||
} | ||
} | ||
|
||
private SeqNo getOrCreate(String id) { | ||
SeqNo seq = db.session().get(SeqNo.class, id, LockMode.PESSIMISTIC_WRITE); | ||
if (seq == null) { | ||
create (id); | ||
seq = db.session().get(SeqNo.class, id, LockMode.PESSIMISTIC_WRITE); | ||
} | ||
return seq; | ||
} | ||
|
||
private void create (String id) { | ||
try (DB db = new DB()) { | ||
db.open(); | ||
db.beginTransaction(); | ||
SeqNo seq = new SeqNo(id); | ||
db.session().save(seq); | ||
db.commit(); | ||
} catch (Exception ignored) { } | ||
} | ||
} |
6 changes: 6 additions & 0 deletions
6
modules/seqno/src/main/resources/META-INF/org/jpos/ee/modules/seqno.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
<module name="seqno"> | ||
<mappings> | ||
<mapping class="org.jpos.ee.SeqNo"/> | ||
</mappings> | ||
</module> | ||
|
Oops, something went wrong.