JCS3CacheImpl.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.cache;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import org.apache.commons.jcs3.JCS;
import org.apache.commons.jcs3.access.CacheAccess;
import org.apache.commons.jcs3.admin.CountingOnlyOutputStream;
import org.apache.commons.jcs3.admin.JCSAdminBean;
import org.apache.commons.jcs3.engine.CacheElementSerialized;
import org.apache.commons.jcs3.engine.behavior.ICacheElement;
import org.apache.commons.jcs3.engine.behavior.ICompositeCacheAttributes;
import org.apache.commons.jcs3.engine.behavior.IElementAttributes;
import org.apache.commons.jcs3.engine.control.CompositeCache;
import org.apache.commons.jcs3.engine.memory.behavior.IMemoryCache;
import org.apache.commons.jcs3.log.LogManager;
import org.openspcoop2.utils.Utilities;
import org.openspcoop2.utils.UtilsException;
import org.openspcoop2.utils.properties.CollectionProperties;
import org.openspcoop2.utils.properties.PropertiesUtilities;
import org.slf4j.Logger;
/**
* JCS3CacheImpl
*
* @author Poli Andrea (apoli@link.it)
* @author $Author$
* @version $Rev$, $Date$
*/
public class JCS3CacheImpl extends AbstractCacheImpl {
public static void setLog4jSystem() {
System.setProperty("jcs.logSystem", LogManager.LOGSYSTEM_LOG4J2);
}
public static boolean initialize(Logger logConsole,Logger logCore,
String cachePropertiesName,
String rootDirectory,Properties objectProperties,
String OPENSPCOOP2_LOCAL_HOME,String OPENSPCOOP2_CACHE_PROPERTIES,String OPENSPCOOP2_CACHE_LOCAL_PATH){
try{
// Originale
java.util.Properties cacheProperties = new java.util.Properties();
java.io.File loggerFile = new java.io.File(rootDirectory+cachePropertiesName);
if(loggerFile .exists() == false ){
cacheProperties.load(Cache.class.getResourceAsStream("/"+cachePropertiesName));
}else{
FileInputStream fin = null;
try{
fin = new java.io.FileInputStream(loggerFile);
cacheProperties.load(fin);
}finally{
try{
if(fin!=null){
fin.close();
}
}catch(Exception eClose){
// close
}
}
}
// File Local Implementation
CollectionProperties cachePropertiesRidefinito =
PropertiesUtilities.searchLocalImplementation(OPENSPCOOP2_LOCAL_HOME,logConsole, OPENSPCOOP2_CACHE_PROPERTIES ,OPENSPCOOP2_CACHE_LOCAL_PATH, rootDirectory);
if(cachePropertiesRidefinito!=null && cachePropertiesRidefinito.size()>0){
Enumeration<?> ridefinito = cachePropertiesRidefinito.keys();
while (ridefinito.hasMoreElements()) {
String key = (String) ridefinito.nextElement();
String value = (String) cachePropertiesRidefinito.get(key);
if(cacheProperties.containsKey(key)){
//Object o =
cacheProperties.remove(key);
}
cacheProperties.put(key, value);
//System.out.println("CHECK NUOVO VALORE: "+loggerProperties.get(key));
}
}
// File Object Implementation
if(objectProperties!=null && objectProperties.size()>0){
Enumeration<?> ridefinito = objectProperties.keys();
while (ridefinito.hasMoreElements()) {
String key = (String) ridefinito.nextElement();
String value = (String) objectProperties.get(key);
if(cacheProperties.containsKey(key)){
//Object o =
cacheProperties.remove(key);
}
cacheProperties.put(key, value);
//System.out.println("CHECK NUOVO VALORE: "+loggerProperties.get(key));
}
}
JCS.setConfigProperties(cacheProperties);
return true;
}catch(Exception e){
logCore.error("Riscontrato errore durante l'inizializzazione del sistema di cache: "
+e.getMessage(),e);
return false;
}
}
private CacheAccess<Object, Serializable> cache = null;
@SuppressWarnings("unused")
private JCSAdminBean cacheAdmin = null;
private JCS3CacheImpl() {
// Metodo usato per le utilities
setLog4jSystem();
this.cacheAdmin = new JCSAdminBean();
}
public JCS3CacheImpl(String name) throws UtilsException{
this(name, org.openspcoop2.utils.cache.Cache.DEFAULT_DISABLE_SYNCRONIZED_GET);
}
@Deprecated
private JCS3CacheImpl(String name, boolean disableSyncronizedGet) throws UtilsException{
super(CacheType.JCS, name);
setLog4jSystem();
try{
this.cacheAdmin = new JCSAdminBean();
this.cache = JCS.getInstance(this.cacheName);
if(disableSyncronizedGet) {
// Dalla versione 3.0 non è più presente la gestione del synchronized
//this.cache.getCacheControl().setSyncDisabled(disableSyncronizedGet);
}
}catch(Exception e){
throw new UtilsException(e.getMessage(),e);
}
}
// *** Inizializzazione ***
@Override
public int getCacheSize() {
return this.cache.getCacheAttributes().getMaxObjects();
}
@Override
public void setCacheSize(int cacheSize) {
// E' necessario prendere l'oggetto e poi risettarlo a causa di un bug.
ICompositeCacheAttributes attr = this.cache.getCacheAttributes();
attr.setMaxObjects(cacheSize);
this.cache.setCacheAttributes(attr);
}
@Override
public CacheAlgorithm getCacheAlgoritm() {
return CacheAlgorithm.toEnum(this.cache.getCacheAttributes().getCacheName());
}
@Override
public void setCacheAlgoritm(CacheAlgorithm cacheAlgoritm) {
// E' necessario prendere l'oggetto e poi risettarlo a causa di un bug.
ICompositeCacheAttributes attr = this.cache.getCacheAttributes();
attr.setCacheName(cacheAlgoritm.getAlgorithm());
this.cache.setCacheAttributes(attr);
}
@Override
public long getItemIdleTime() throws UtilsException {
try{
return this.cache.getDefaultElementAttributes().getIdleTime();
}catch(Exception e){
throw new UtilsException(e.getMessage(),e);
}
}
@Override
public void setItemIdleTime(long itemIdleTimeCache) throws UtilsException {
try{
// E' necessario prendere l'oggetto e poi risettarlo a causa di un bug.
IElementAttributes el = this.cache.getDefaultElementAttributes();
el.setIdleTime(itemIdleTimeCache);
this.cache.setDefaultElementAttributes(el);
}catch(Exception e){
throw new UtilsException(e.getMessage(),e);
}
}
@Override
public long getItemLifeTime() throws UtilsException {
try{
return this.cache.getDefaultElementAttributes().getMaxLife();
}catch(Exception e){
throw new UtilsException(e.getMessage(),e);
}
}
@Override
public boolean isEternal() throws UtilsException {
try{
return this.cache.getDefaultElementAttributes().getIsEternal();
}catch(Exception e){
throw new UtilsException(e.getMessage(),e);
}
}
@Override
public void setItemLifeTime(long itemLifeTimeCache) throws UtilsException {
try{
// E' necessario prendere l'oggetto e poi risettarlo a causa di un bug.
IElementAttributes el = this.cache.getDefaultElementAttributes();
if(itemLifeTimeCache>0) {
el.setIsEternal(false);
el.setMaxLife(itemLifeTimeCache);
}
else {
el.setIsEternal(true);
el.setMaxLife(-1);
}
this.cache.setDefaultElementAttributes(el);
}catch(Exception e){
throw new UtilsException(e.getMessage(),e);
}
}
@Override
public void build() throws UtilsException{
// nop; le operazioni di aggiustamento delle dimensioni, si fanno direttamente sugli oggetti
}
// *** Gestione ***
@Override
public void clear() throws UtilsException{
if(this.cache!=null){
try{
this.cache.clear();
}catch(Exception e){
throw new UtilsException(e.getMessage(),e);
}
}
}
@Override
public Object get(String key){
return this.cache.get(this.formatKeyCache(key));
}
@Override
public void remove(String key) throws UtilsException{
try{
this.cache.remove(this.formatKeyCache(key));
}catch(Exception e){
throw new UtilsException(e.getMessage(),e);
}
}
@Override
public void put(String key,org.openspcoop2.utils.cache.CacheResponse value) throws UtilsException{
try{
this.cache.put(this.formatKeyCache(key), value);
}catch(Exception e){
throw new UtilsException(e.getMessage(),e);
}
}
@Override
public void put(String key,Serializable value) throws UtilsException{
try{
this.cache.put(this.formatKeyCache(key), value);
}catch(Exception e){
throw new UtilsException(e.getMessage(),e);
}
}
private String formatKeyCache(String key) {
// org/apache/commons/jcs/engine/control/CompositeCache.java
// if ( cacheElement.getKey() instanceof String
// && cacheElement.getKey().toString().endsWith( NAME_COMPONENT_DELIMITER ) )
// {
// throw new IllegalArgumentException( "key must not end with " + NAME_COMPONENT_DELIMITER
// + " for a put operation" );
// }
//
// Dove in org.apache.commons.jcs3.engine.behavior.ICache
// public static final String NAME_COMPONENT_DELIMITER = ":";
/*StringBuilder bf = new StringBuilder(key);
if(bf.toString().endsWith(org.apache.commons.jcs3.engine.behavior.ICache.NAME_COMPONENT_DELIMITER)){
bf.append("_");
}
return bf.toString();*/
if(key.endsWith(org.apache.commons.jcs3.engine.behavior.ICache.NAME_COMPONENT_DELIMITER)) {
return key+"_";
}
else {
return key;
}
}
@Override
public int getItemCount() throws UtilsException {
CompositeCache<Object, Serializable> cache = JCSAdminBean.getCompositeCacheManager().getCache(this.cacheName);
return cache.getSize();
}
@Override
public List<String> keys() throws UtilsException {
try{
List<String> keys = new ArrayList<>();
Serializable[] keysObject = this.cache.getCacheControl().getKeySet().toArray(new Serializable[0]);
if(keysObject!=null){
for (int i = 0; i < keysObject.length; i++) {
keys.add((String)keysObject[i]);
}
}
return keys;
}catch(Exception e){
throw new UtilsException(e.getMessage(),e);
}
}
// *** Info ***
@Override
public void printStats(OutputStream out, String separator) throws UtilsException{
this.printStats(this.cacheName,out,separator,true);
}
private void printStats(String cacheName, OutputStream out, String separator, boolean thisCache) throws UtilsException {
try{
StringBuilder bf = new StringBuilder();
CompositeCache<Object, Serializable> cache = JCSAdminBean.getCompositeCacheManager().getCache(cacheName);
//bf.append(Utilities.convertBytesToFormatString(region.getByteCount()));
// NOTA: e' stato re-implementato il metodo poiche' casualmente avveniva un errore del tipo:
// Problem getting byte count. Likley cause is a non serilizable object.null
// Il problema derivava dall'implementazione del metodo all'interno della classe org/apache/commons/jcs/admin/JCSAdminBean
// Viene utilizzato l'iterator dentro una struttura dinamica che cambia.
// A volte, quando poi veniva registrato l'errore soprastante, avveniva questo errore (scoperto aggiungendo stampe nelle classi di JCS)
// java.util.ConcurrentModificationException
// at java.util.Hash table$Enumerator.next(Hash table.java:1031)
// at org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache$IteratorWrapper.next(LRUMemoryCache.java:428)
// at org.apache.commons.jcs3.admin.JCSAdminBean.getByteCount(JCSAdminBean.java:95)
int tentativi = 0;
long sizeAttuale = -1;
while (tentativi<10) {
sizeAttuale = this.getByteCount(cache);
if(this.errorOccursCountingBytes==false){
break;
}
if(thisCache){
//System.err.println("PROVO ALTRO TENTATIVO");
tentativi++;
cache = JCSAdminBean.getCompositeCacheManager().getCache(cacheName);
}
else{
break;
}
}
bf.append("Nome:");
bf.append(cacheName);
bf.append(" ");
bf.append(separator);
bf.append("Tipo:");
bf.append(CacheType.JCS);
bf.append(" ");
bf.append(separator);
bf.append("Stato:");
bf.append(cache.getStatus());
bf.append(" ");
bf.append(separator);
// Dalla versione 3.0 non è più presente la gestione del synchronized
// bf.append("GetSyncDisabled:");
// bf.append(cache.isSyncDisabled());
// bf.append(" ");
//
// bf.append(separator);
if(cache.getCacheAttributes()!=null){
bf.append("Algoritmo:");
String cacheAlgoName = cache.getCacheAttributes().getCacheName();
CacheAlgorithm cacheEnum = CacheAlgorithm.toEnum(cacheAlgoName);
if(cacheEnum!=null){
bf.append(cacheEnum.name());
}else{
bf.append(cacheAlgoName);
}
bf.append(" ");
bf.append(separator);
bf.append("Dimensione:");
bf.append(cache.getCacheAttributes().getMaxObjects());
bf.append(" ");
bf.append(separator);
}
bf.append("ElementiInCache:");
bf.append(cache.getSize());
bf.append(" ");
bf.append(separator);
bf.append("MemoriaOccupata:");
if(this.errorOccursCountingBytes){
bf.append("[WARN !Error occurs counting bytes, re-try!]");
}
bf.append(Utilities.convertBytesToFormatString(sizeAttuale));
bf.append(" ");
bf.append(separator);
if(cache.getElementAttributes()!=null){
bf.append("IdleTime:");
long idleTime = cache.getElementAttributes().getIdleTime();
if(idleTime>0){
bf.append(Utilities.convertSystemTimeIntoStringMillisecondi(idleTime*1000,false));
}
else if(idleTime==0){
bf.append("0");
}
else if(idleTime<0){
bf.append("Infinito");
}
bf.append(" ");
bf.append(separator);
bf.append("LifeTime:");
long lifeTime = cache.getElementAttributes().getMaxLife();
if(lifeTime>0){
bf.append(Utilities.convertSystemTimeIntoStringMillisecondi(lifeTime*1000,false));
}
else if(lifeTime==0){
bf.append("0");
}
else if(lifeTime<0){
if(cache.getElementAttributes().getIsEternal()) {
bf.append("Infinito");
}
else {
bf.append("Infinito (NoEternal?)");
}
}
bf.append(" ");
bf.append(separator);
}
bf.append("PutCount:");
bf.append(cache.getUpdateCount());
bf.append(" ");
bf.append(separator);
bf.append("HitCount(Aux):");
bf.append(cache.getHitCountAux());
bf.append(" ");
bf.append(separator);
bf.append("HitCount(Ram):");
bf.append(cache.getHitCountRam());
bf.append(" ");
bf.append(separator);
bf.append("MissCount(Expired):");
bf.append(cache.getMissCountExpired());
bf.append(" ");
bf.append(separator);
bf.append("MissCount(NotFound):");
bf.append(cache.getMissCountNotFound());
bf.append(" ");
out.write(bf.toString().getBytes());
}catch(Exception e){
throw new UtilsException(e.getMessage(),e);
}
}
private boolean errorOccursCountingBytes_debug = false;
private boolean errorOccursCountingBytes = false;
public <K, V> long getByteCount(CompositeCache<K, V> cache)
{
this.errorOccursCountingBytes = false;
if (cache == null)
{
throw new IllegalArgumentException("The cache object specified was null.");
}
long size = 0;
IMemoryCache<K, V> memCache = cache.getMemoryCache();
try {
for (K key : memCache.getKeySet())
{
ICacheElement<K, V> ice = null;
try
{
ice = memCache.get(key);
}
catch (IOException e)
{
//Modificato per openspcoop
if(this.errorOccursCountingBytes_debug) {
System.err.println("["+this.cacheName+"] Element cache get");
e.printStackTrace(System.err);
}
this.errorOccursCountingBytes = true;
continue;
//throw new RuntimeException("IOException while trying to get a cached element", e);
}
if (ice == null)
{
continue;
}
if (ice instanceof CacheElementSerialized)
{
size += ((CacheElementSerialized<K, V>) ice).getSerializedValue().length;
}
else
{
Object element = ice.getVal();
if(element == null) {
//Modificato per openspcoop
if(this.errorOccursCountingBytes_debug) {
System.err.println("["+this.cacheName+"] Element cache is null");
}
this.errorOccursCountingBytes = true;
continue;
}
//CountingOnlyOutputStream: Keeps track of the number of bytes written to it, but doesn't write them anywhere.
CountingOnlyOutputStream counter = new CountingOnlyOutputStream();
try (ObjectOutputStream out = new ObjectOutputStream(counter);)
{
out.writeObject(element);
}
catch (IOException e)
{
// Modificato per openspcoop
if(this.errorOccursCountingBytes_debug) {
System.err.println("["+this.cacheName+"] Element cache writeObject ("+element.getClass().getName()+")");
e.printStackTrace(System.err);
}
this.errorOccursCountingBytes = true;
continue;
//throw new RuntimeException("IOException while trying to measure the size of the cached element", e);
}
finally
{
try
{
counter.close();
}
catch (IOException e)
{
// ignore
}
}
// 4 bytes lost for the serialization header
size += counter.getCount() - 4;
}
}
}
catch ( Exception e )
{
System.err.println( "Problem getting byte count (Modified by GovWay). Likley cause is a non serilizable object." + e.getMessage() );
e.printStackTrace(System.err);
}
return size;
}
public static String printAllStats(String separatorStat, String separatorCache) throws UtilsException {
try{
ByteArrayOutputStream bout = new ByteArrayOutputStream();
printAllStats(bout,separatorStat,separatorCache);
bout.flush();
bout.close();
return bout.toString();
}catch(Exception e){
throw new UtilsException(e.getMessage(),e);
}
}
public static void printAllStats(OutputStream out, String separatorStat, String separatorCache) throws UtilsException {
try{
JCS3CacheImpl cacheUtility = new JCS3CacheImpl();
Set<String> cacheNames_tmp = JCSAdminBean.getCompositeCacheManager().getCacheNames();
if(cacheNames_tmp.size()>0) {
String[] cacheNames = new String[cacheNames_tmp.size()];
int index = 0;
for (String name : cacheNames_tmp) {
cacheNames[index] = name;
index++;
}
Arrays.sort( cacheNames );
for ( int i = 0; i < cacheNames.length; i++ ) {
cacheUtility.printStats(cacheNames[i],out,separatorStat,false);
out.write(separatorCache.getBytes());
}
}
}catch(Exception e){
throw new UtilsException(e.getMessage(),e);
}
}
// *** Sincronizzazione ***
@SuppressWarnings("deprecation")
@Override
@Deprecated
public void disableSyncronizedGet() throws UtilsException {
if(this.cache==null) {
throw new UtilsException("Cache disabled");
}
try{
// Dalla versione 3.0 non è più presente la gestione del synchronized
//this.cache.getCacheControl().setSyncDisabled(true);
}catch(Exception e){
throw new UtilsException(e.getMessage(),e);
}
}
@SuppressWarnings("deprecation")
@Override
@Deprecated
public boolean isDisableSyncronizedGet() throws UtilsException {
if(this.cache==null) {
throw new UtilsException("Cache disabled");
}
try{
// Dalla versione 3.0 non è più presente la gestione del synchronized
//return this.cache.getCacheControl().isSyncDisabled();
return true;
}catch(Exception e){
throw new UtilsException(e.getMessage(),e);
}
}
@SuppressWarnings("deprecation")
@Override
@Deprecated
public void enableDebugSystemOut() throws UtilsException {
if(this.cache==null) {
throw new UtilsException("Cache disabled");
}
try{
// Dalla versione 3.0 non è più presente la gestione del synchronized
//this.cache.getCacheControl().setDebugSystemOut(true);
}catch(Exception e){
throw new UtilsException(e.getMessage(),e);
}
}
@SuppressWarnings("deprecation")
@Override
@Deprecated
public boolean isEnableDebugSystemOut() throws UtilsException {
if(this.cache==null) {
throw new UtilsException("Cache disabled");
}
try{
// Dalla versione 3.0 non è più presente la gestione del synchronized
//return this.cache.getCacheControl().isDebugSystemOut();
return false;
}catch(Exception e){
throw new UtilsException(e.getMessage(),e);
}
}
}