Semaphore.java

/*
 * GovWay - A customizable API Gateway 
 * https://govway.org
 * 
 * Copyright (c) 2005-2024 Link.it srl (https://link.it). 
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3, as published by
 * the Free Software Foundation.
 *
 * 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package org.openspcoop2.utils;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Semaphore
 *
 *
 * @author Poli Andrea (apoli@link.it)
 * @author $Author$
 * @version $Rev$, $Date$
 */
public class Semaphore {

	private static long TIMEOUT_MS = 30000;
	public static long getTIMEOUT_MS() {
		return TIMEOUT_MS;
	}
	public static void setTIMEOUT_MS(long tIMEOUT_MS) {
		TIMEOUT_MS = tIMEOUT_MS;
	}

	private static boolean DEBUG = false;
	public static boolean isDEBUG() {
		return DEBUG;
	}
	public static void setDEBUG(boolean dEBUG) {
		DEBUG = dEBUG;
	}

	private static SemaphoreType semaphoreType = SemaphoreType.Semaphore;
	public static SemaphoreType getSemaphoreType() {
		return semaphoreType;
	}
	public static void setSemaphoreType(SemaphoreType semaphoreType) {
		Semaphore.semaphoreType = semaphoreType;
	}

	private static boolean fair = true;
	public static boolean isFair() {
		return fair;
	}
	public static void setFair(boolean fair) {
		Semaphore.fair = fair;
	}

	private final java.util.concurrent.Semaphore semaphore;
	private final java.util.concurrent.locks.ReentrantLock reentrantLock;
	private String semaphoreName = null;
	private int permits = -1;
	
	public Semaphore(String name) {
		this(name, Semaphore.semaphoreType, Semaphore.fair);
	}
	public Semaphore(String name, boolean fair) {
		this(name, Semaphore.semaphoreType, fair);
	}
	public Semaphore(String name, SemaphoreType semaphoreType, boolean fair) {
		this.semaphoreName = name;
		switch (semaphoreType) {
		case ReentrantLock:
			this.reentrantLock = new ReentrantLock(fair); 
			this.semaphore = null;
			break;
		case Semaphore:
			this.reentrantLock = null; 
			this.semaphore = new java.util.concurrent.Semaphore(1, fair);
			break;
		default:
			this.reentrantLock = null;
			this.semaphore = null;
		}
		this.permits = 1;
	}
	
	public Semaphore(String name, int permits) {
		this(name, permits, Semaphore.fair);
	}
	public Semaphore(String name, int permits, boolean fair) {
		this.semaphoreName = name;
		this.semaphore = new java.util.concurrent.Semaphore(permits, fair);
		this.reentrantLock = null;
		this.permits = permits;
	}
	
	public boolean hasQueuedThreads() {
		if(this.semaphore!=null) {
			return this.semaphore.hasQueuedThreads();
		}
		else {
			return this.reentrantLock.hasQueuedThreads();
		}
	}
	public boolean available() {
		if(this.semaphore!=null) {
			return this.semaphore.availablePermits()>0;
		}
		else {
			return !this.reentrantLock.isLocked();
		}
	}
	public int availablePermits() {
		if(this.semaphore!=null) {
			return this.semaphore.availablePermits();
		}
		else {
			return -1;
		}
	}
	
	private String getPrefix(String methodName, String idTransazione) {
		String idTr = "";
		if(idTransazione!=null) {
			idTr = " (idTransazione:"+idTransazione+")";
		}
		return this.semaphoreName+"."+methodName+" [Thread:"+Thread.currentThread().getName()+"]"+idTr+" ";
	}
	
	public void acquire(String methodName) throws UtilsException {
		this.acquire(methodName, null);
	}
	public void acquire(String methodName, String idTransazione) throws UtilsException {
		try {
			if(TIMEOUT_MS<=0) {
				if(DEBUG) {
					System.out.println(getPrefix(methodName, idTransazione)+" acquire ...");
				}
				if(this.semaphore!=null) {
					this.semaphore.acquire();
				}
				else {
					this.reentrantLock.lock();
				}
				if(DEBUG) {
					System.out.println(getPrefix(methodName, idTransazione)+" acquired");
				}
			}
			else {
				if(DEBUG) {
					System.out.println(getPrefix(methodName, idTransazione)+" acquire("+TIMEOUT_MS+"ms) ...");
				}
				boolean acquire = false;
				if(this.semaphore!=null) {
					acquire = this.semaphore.tryAcquire(TIMEOUT_MS, TimeUnit.MILLISECONDS);
				}
				else {
					acquire = this.reentrantLock.tryLock(TIMEOUT_MS, TimeUnit.MILLISECONDS);
				}
				if(!acquire) {
					throw new InterruptedException("["+this.semaphoreName+"] Could not acquire semaphore after "+TIMEOUT_MS+"ms");
				}
				if(DEBUG) {
					System.out.println(getPrefix(methodName, idTransazione)+" acquired");
				}
			}
		}catch(InterruptedException ie) {
			if(DEBUG) {
				System.out.println(getPrefix(methodName, idTransazione)+" acquire("+TIMEOUT_MS+"ms) failed: "+ie.getMessage());
			}
			throw new UtilsException(ie.getMessage(),ie);
		}
	}
	
	public void acquireThrowRuntime(String methodName) throws SemaphoreRuntimeException {
		this.acquireThrowRuntime(methodName, null);
	}
	public void acquireThrowRuntime(String methodName, String idTransazione) throws SemaphoreRuntimeException {
		try {
			this.acquire(methodName, idTransazione);
		}
		catch(Throwable t) {
			throw new SemaphoreRuntimeException(t.getMessage(),t);
		}
	}
	
	public void release(String methodName) {
		this.release(methodName, null);
	}
	public void release(String methodName, String idTransazione) {
		if(DEBUG) {
			System.out.println(getPrefix(methodName, idTransazione)+" release ...");
		}
		if(this.semaphore!=null) {
			if(this.semaphore.availablePermits()<this.permits) {
				this.semaphore.release(); // altrimenti ogni release utilizzato male fa incrementare i permessi
			}
		}
		else {
			this.reentrantLock.unlock();
		}
		if(DEBUG) {
			System.out.println(getPrefix(methodName, idTransazione)+" released");
		}
	}
}

@SuppressWarnings("serial")
class SemaphoreRuntimeException extends RuntimeException{

	public SemaphoreRuntimeException() {
		super();
	}

	public SemaphoreRuntimeException(String message, Throwable cause, boolean enableSuppression,
			boolean writableStackTrace) {
		super(message, cause, enableSuppression, writableStackTrace);
	}

	public SemaphoreRuntimeException(String message, Throwable cause) {
		super(message, cause);
	}

	public SemaphoreRuntimeException(String message) {
		super(message);
	}

	public SemaphoreRuntimeException(Throwable cause) {
		super(cause);
	}
	
}