/********************************************************************** * This file is part of iDempiere ERP Open Source * * http://www.idempiere.org * * * * Copyright (C) Contributors * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License * * as published by the Free Software Foundation; either version 2 * * 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * * Contributors: * * - Trek Global Corporation * * - Heng Sin Low * **********************************************************************/ package org.idempiere.cache; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.Properties; import java.util.function.UnaryOperator; import org.compiere.model.PO; import org.compiere.util.CCache; import org.compiere.util.Env; import org.compiere.util.Util; /** * Thread safe PO cache. For thread safety, only PO with thread local context (po.getCtx() == Env.getCtx() and without trxName is keep in cache.
* PO is mark immutable before being added to cache. If the pass in PO doesn't match the 2 condition, a copy of the PO is added to cache instead.
* For get operation, if request is being make with non thread local context (ctx != Env.getCtx()) or with trxName, a copy of the PO from cache * is return instead. * * @author hengsin */ public class ImmutablePOCache extends CCache { /** * generated serial id */ private static final long serialVersionUID = -3342469152066078741L; /** * @param name * @param initialCapacity * @param expireMinutes * @param distributed * @param maxSize */ public ImmutablePOCache(String name, int initialCapacity, int expireMinutes, boolean distributed, int maxSize) { super(name, initialCapacity, expireMinutes, distributed, maxSize); } /** * @param name * @param initialCapacity * @param expireMinutes * @param distributed */ public ImmutablePOCache(String name, int initialCapacity, int expireMinutes, boolean distributed) { super(name, initialCapacity, expireMinutes, distributed); } /** * @param name * @param initialCapacity * @param expireMinutes */ public ImmutablePOCache(String name, int initialCapacity, int expireMinutes) { super(name, initialCapacity, expireMinutes); } /** * @param name * @param initialCapacity */ public ImmutablePOCache(String name, int initialCapacity) { super(name, initialCapacity); } /** * @param tableName * @param name * @param initialCapacity * @param distributed */ public ImmutablePOCache(String tableName, String name, int initialCapacity, boolean distributed) { super(tableName, name, initialCapacity, distributed); } /** * @param tableName * @param name * @param initialCapacity * @param expireMinutes * @param distributed * @param maxSize */ public ImmutablePOCache(String tableName, String name, int initialCapacity, int expireMinutes, boolean distributed, int maxSize) { super(tableName, name, initialCapacity, expireMinutes, distributed, maxSize); } /** * @param tableName * @param name * @param initialCapacity * @param expireMinutes * @param distributed */ public ImmutablePOCache(String tableName, String name, int initialCapacity, int expireMinutes, boolean distributed) { super(tableName, name, initialCapacity, expireMinutes, distributed); } /** * @param tableName * @param name * @param initialCapacity */ public ImmutablePOCache(String tableName, String name, int initialCapacity) { super(tableName, name, initialCapacity); } @Override public V put(K key, V value) { return put(key, value, (UnaryOperator)null); } /** * PO is mark immutable and add to cache if it is without trxName and with thread local context (i.e po.getCtx() == Env.getCtx()).
* If either of the condition is not true, a copy of the PO will be created and add to cache using the pass in copyOperator or * through copy constructor (through reflection) if copyOperator parameter is null (exception is throw if both copyOperator and * copy constructor is not available). * @param key * @param po * @param copyOperator operator to call copy constructor if po has transaction name or po.getCtx() != Env.getCtx() * @return po or the copy of po that have been added to cache */ public V put(K key, V po, UnaryOperator copyOperator) { if (po == null) { super.put(key, po); return null; } po.markImmutable(); if (Util.isEmpty(po.get_TrxName(), true) && po.getCtx() == Env.getCtx()) { super.put(key, po); return po; } else if (copyOperator == null) { try { V copy = null; try { copy = copyOf(Env.getCtx(), po); } catch (Exception e) {} if (copy == null) copy = copyOf(Env.getCtx(), po, (String)null); if (copy != null) { super.put(key, copy); return copy; } throw new RuntimeException("No copy constructor for " + po.getClass().getName()); } catch (NoSuchMethodException | SecurityException | InvocationTargetException | IllegalAccessException | InstantiationException e) { throw new RuntimeException("Error calling copy constructor for " + po.getClass().getName() + " : " + e.getMessage(), e); } } else { V copy = copyOperator.apply(po); copy.markImmutable(); super.put(key, copy); return copy; } } @SuppressWarnings("unchecked") @Override public V get(Object key) { try { return get((Properties)null, (K)key); } catch (ClassCastException e) { return null; } } /** * @param ctx context * @param key * @return value for key */ public V get(Properties ctx, K key) { return get(ctx, key, (UnaryOperator)null); } /** * Get PO from cache. If ctx != Env.getCtx() or trxName is not empty, a copy of the PO is return instead * @param ctx context * @param key * @param copyOperator operator to call copy constructor when ctx != po.getCtx() or transaction name is not empty * @return PO from cache (if there's match for key) */ @SuppressWarnings("unchecked") public V get(Properties ctx, K key, UnaryOperator copyOperator) { V value = super.get(key); if (value == null) return null; if (ctx == null) ctx = Env.getCtx(); if (ctx != value.getCtx()) { if (copyOperator == null) { //use reflection to find copy constructor try { try { V copy = copyOf(ctx, value); if (copy != null) return copy; } catch (Exception e) {} V copy = copyOf(ctx, value, (String)null); if (copy != null) return copy; throw new RuntimeException("No copy constructor for " + value.getClass().getName()); } catch (NoSuchMethodException | SecurityException | InvocationTargetException | IllegalAccessException | InstantiationException e) { throw new RuntimeException("Error calling copy constructor for " + value.getClass().getName() + " : " + e.getMessage(), e); } } else { V copy = copyOperator.apply(value); return (V) copy.markImmutable(); } } else { return value; } } /** * Create a copy of value using copy constructor * @param ctx * @param value * @param trxName * @return copy of value or null * @throws NoSuchMethodException * @throws InstantiationException * @throws IllegalAccessException * @throws InvocationTargetException */ @SuppressWarnings("unchecked") private V copyOf(Properties ctx, V value, String trxName) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { Constructor copyConstructor; copyConstructor = value.getClass().getDeclaredConstructor(Properties.class, value.getClass(), String.class); if (copyConstructor != null) { V copy = (V) copyConstructor.newInstance(ctx, value, trxName); return (V) copy.markImmutable(); } return null; } /** * Create a copy of value using copy constructor * @param ctx * @param value * @return copy of value or null * @throws NoSuchMethodException * @throws InstantiationException * @throws IllegalAccessException * @throws InvocationTargetException */ @SuppressWarnings("unchecked") private V copyOf(Properties ctx, V value) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { Constructor copyConstructor; copyConstructor = value.getClass().getDeclaredConstructor(Properties.class, value.getClass()); if (copyConstructor != null) { V copy = (V) copyConstructor.newInstance(ctx, value); return (V) copy.markImmutable(); } return null; } }