/*******************************************************************************
* Copyright (c) 2007-2014, D. Lutz and Elexis.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* D. Lutz - initial API and implementation
* N. Giger - Moved KonsTextLock to separate file, fixed lock for Elexis 3.0
*
* Sponsors:
* Dr. Peter Schönbucher, Luzern
******************************************************************************/
package org.iatrix.data;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Date;
import org.iatrix.Iatrix;
import org.iatrix.views.JournalView;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.elexis.core.data.activator.CoreHub;
import ch.elexis.core.ui.util.CreatePrescriptionHelper;
import ch.elexis.data.Anwender;
import ch.elexis.data.Konsultation;
import ch.elexis.data.PersistentObject;
import ch.rgw.tools.JdbcLink;
import ch.rgw.tools.JdbcLink.Stm;
import ch.rgw.tools.JdbcLinkException;
import ch.rgw.tools.StringTool;
/**
* Handle locking of kons text to avoid access of multiple users/instances at the same time
*
* @author danlutz
*/
public class KonsTextLock {
// unique value for this instance
private final String identifier;
String konsultationId = null;
String userId = null;
String key = null;
String label = null;
boolean lockVar = false; // used only if CFG_USE_KONSTEXT_LOCKING is true
boolean traceEnabled = false;
boolean useDatabaseLock = false;
private static final long MAX_AGE_2_HOURS = 2 * 1000L * 3600L; // 2 hours in milliseconds
private final static Logger logger = LoggerFactory.getLogger("org.iatrix.KonsTextLock");
public static void deleteObsoleteLocks(Konsultation konsultation) {
int nrFound = 0;
String sql = "SELECT param, wert FROM CONFIG WHERE param like '%_konslock_%'";
ResultSet values = null;
PreparedStatement pstm = PersistentObject.getDefaultConnection()
.getPreparedStatement(sql);
try {
values = pstm.executeQuery();
long now = System.currentTimeMillis();
StringBuilder delStm = new StringBuilder("delete from config where param in (");
while (values.next()) {
String key = values.getString(1);
String oldlock = values.getString(2);
long locktime = 0;
String[] tokens = oldlock.split("#");
if (tokens.length >= 1) {
try {
locktime = Long.parseLong(tokens[0]);
} catch (NumberFormatException ex) {
break;
}
}
long age = now - locktime;
String msg = oldlock + " " + locktime + " from " + new Date(locktime).toString();
logger.trace(msg);
if (age > MAX_AGE_2_HOURS) { // Älter als zwei Stunden -> Löschen
if (nrFound == 0) {
delStm.append(" '" + key + "' ");
} else {
delStm.append(", '" + key + "' ");
}
nrFound++;
}
}
if (nrFound> 0 ) {
logger.info("Deleting " + nrFound + " obsolete _konslock_ from config");
delStm.append(")");
Stm stm = PersistentObject.getDefaultConnection().getStatement();
stm.exec(delStm.toString());
PersistentObject.getDefaultConnection().releaseStatement(stm);
}
} catch (SQLException | JdbcLinkException e) {
logger.info(" Error deleting " + e );
LoggerFactory.getLogger(CreatePrescriptionHelper.class)
.error("Could not find any konslock", e);
} finally {
PersistentObject.getDefaultConnection().releasePreparedStatement(pstm);
}
}
/*
* Trace, but prepend some common information
*/
private void trace(String msg){
if (traceEnabled)
logger.info(msg + key + " u: " + userId );
}
// constructor
public KonsTextLock(Konsultation konsultation, Anwender user){
identifier = StringTool.unique(JournalView.ID);
traceEnabled =
CoreHub.userCfg.get(Iatrix.CFG_USE_KONSTEXT_TRACE,
Iatrix.CFG_USE_KONSTEXT_TRACE_DEFAULT);
useDatabaseLock =
CoreHub.userCfg.get(Iatrix.CFG_USE_KONSTEXT_LOCKING,
Iatrix.CFG_USE_KONSTEXT_LOCKING_DEFAULT);
konsultationId = konsultation.getId();
userId = user.getId();
// create key name
StringBuffer sb = new StringBuffer();
sb.append(JournalView.ID).append("_").append("konslock").append("_").append(konsultationId);
key = sb.toString();
trace("new KonsTextLock: ");
lockVar = false;
}
public Value getLockValue(){
String lockValue =
PersistentObject.getDefaultConnection().queryString(
"SELECT wert from CONFIG WHERE param = " + JdbcLink.wrap(key));
return new Value(lockValue);
}
/**
* getKey
* @return the key for the Kons (user and kons.getId())
*/
public String getKey() {
return key;
}
/**
* getKey
* @return human readable representation
*/
public String getLabel() {
return label;
}
// taken from PersistentObject
// return true if lock is ok, false else
// may also be called to update a lock
// a lock older than 2 hours is considered as outdated
// therefore, a lock must regularly be updated
public synchronized boolean lock(){
if (useDatabaseLock == false) {
lockVar = true;
return lockVar;
}
trace("lock: start ");
Stm stm = PersistentObject.getDefaultConnection().getStatement();
try {
long now = System.currentTimeMillis();
// Gibt es das angeforderte Lock schon?
String oldlock =
stm.queryString("SELECT wert FROM CONFIG WHERE param=" + JdbcLink.wrap(key));
if (!StringTool.isNothing(oldlock)) {
// Ja, wie alt ist es?
Value lockValue = new Value(oldlock);
String lockIdentifier = lockValue.getIdentifier();
if (lockIdentifier != null && lockIdentifier.equals(identifier)) {
// it's our own lock. update timestamp
lockValue = new Value(userId, identifier);
// System.err.println("DEBUG: lock() update: time = " +
// lockValue.getTimestamp());
String lockstring = lockValue.getLockString();
StringBuilder sb = new StringBuilder();
sb.append("UPDATE CONFIG SET wert = ").append("'" + lockstring + "'")
.append(" WHERE param = ").append(JdbcLink.wrap(key));
stm.exec(sb.toString());
trace("lock: okay ");
return true;
}
long locktime = lockValue.getTimestamp();
long age = now - locktime;
if (age > MAX_AGE_2_HOURS) { // Älter als zwei Stunden -> Löschen
// System.err.println("DEBUG: lock() giving up: age = " + age);
stm.exec("DELETE FROM CONFIG WHERE param=" + JdbcLink.wrap(key));
} else {
label = key + " " + oldlock + " " + locktime + " from " + new Date(locktime).toString();
trace("lock: failed " + label);
return false;
}
}
// Neues Lock erstellen
Value lockValue = new Value(userId, identifier);
long timestamp = lockValue.getTimestamp();
label = lockValue.hostName + "_" + userId + " " + timestamp + " from " + new Date(timestamp).toString();
// System.err.println("DEBUG: lock() insert: time = " + lockValue.getTimestamp());
String lockstring = lockValue.getLockString();
StringBuilder sb = new StringBuilder();
sb.append("INSERT INTO CONFIG (param, wert) VALUES (").append(JdbcLink.wrap(key))
.append(",").append("'").append(lockstring).append("')");
stm.exec(sb.toString());
// Prüfen, ob wir es wirklich haben, oder ob doch jemand anders schneller war.
String check =
stm.queryString("SELECT wert FROM CONFIG WHERE param=" + JdbcLink.wrap(key));
if (check == null || !check.equals(lockstring)) {
// lock doesn't exist or has wrong value
trace("lock: failed ");
return false;
}
trace("lock: okay " + label);
return true;
} finally {
PersistentObject.getDefaultConnection().releaseStatement(stm);
}
}
/**
* Check wheter we own the lock
*
* @return true if we own the lock, false otherwise
*/
public synchronized boolean isLocked(){
if (useDatabaseLock == false) {
return lockVar;
}
Value lockValue = getLockValue();
return (identifier.equals(lockValue.getIdentifier()));
}
// taken from PersistentObject
public synchronized boolean unlock(){
if (useDatabaseLock == false) {
lockVar = false;
return lockVar;
}
trace("unlock: start ");
String lock =
PersistentObject.getDefaultConnection().queryString(
"SELECT wert from CONFIG WHERE param=" + JdbcLink.wrap(key));
if (StringTool.isNothing(lock)) {
trace("unlock: failed ");
return false;
}
Value lockValue = new Value(lock);
String lockIdentifier = lockValue.getIdentifier();
if (lockIdentifier != null && lockIdentifier.equals(identifier)) {
PersistentObject.getDefaultConnection().exec(
"DELETE FROM CONFIG WHERE param=" + JdbcLink.wrap(key));
trace("unlock: okay ");
return true;
}
if (traceEnabled)
trace("unlock: failed ");
return false;
}
public class Value {
private long timestamp = 0;
private String userId = null;
private String identifier = null;
private String hostName = null;
Value(String lockValue){
// format: <timestamp>#<userid>#<identifier>#<hostname>
if (lockValue != null) {
String[] tokens = lockValue.split("#");
if (tokens.length >= 3) {
try {
timestamp = Long.parseLong(tokens[0]);
} catch (NumberFormatException ex) {
return;
}
userId = tokens[1];
identifier = tokens[2];
// Allow interaction with old 2.1.7 and older 3.0
if (tokens.length >= 4) {
hostName = tokens[3];
}
}
}
}
Value(String userId, String identifier){
timestamp = System.currentTimeMillis();
this.userId = userId;
this.identifier = identifier;
try {
this.hostName = InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException e) {
this.hostName = null;
}
}
String getLockString(){
StringBuffer sb = new StringBuffer();
sb.append(new Long(timestamp).toString()).append("#").append(userId).append("#")
.append(identifier).append("#").append(hostName);
return sb.toString();
}
long getTimestamp(){
return timestamp;
}
/**
* Return the Anwender owning this lock
*
* @return Anwender owning the lock, or null if not found
*/
public Anwender getUser(){
return Anwender.load(userId);
}
/**
* Return the hostname owning this lock
*
* @return hostowning the lock, or null if not found
*/
public String getHost(){
return hostName;
}
String getIdentifier(){
return identifier;
}
}
}