/* * JBoss, Home of Professional Open Source. * Copyright 2008, Red Hat Middleware LLC, and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.ejb.plugins.cmp.jdbc2.schema; import org.jboss.system.ServiceMBeanSupport; import javax.transaction.Transaction; /** * @author <a href="mailto:alex@jboss.org">Alexey Loubyansky</a> * @version <tt>$Revision: 81030 $</tt> * @jmx:mbean extends="org.jboss.system.ServiceMBean" */ public class PartitionedTableCache extends ServiceMBeanSupport implements Cache, PartitionedTableCacheMBean { private Cache.Listener listener = Cache.Listener.NOOP; private final int minCapacity; private final int minPartitionCapacity; private int maxCapacity; private int maxPartitionCapacity; private final TableCache[] partitions; private Overager overager; public PartitionedTableCache(int minCapacity, int maxCapacity, int partitionsTotal) { this.minCapacity = minCapacity; this.maxCapacity = maxCapacity; minPartitionCapacity = minCapacity / partitionsTotal + 1; maxPartitionCapacity = maxCapacity / partitionsTotal + 1; partitions = new TableCache[partitionsTotal]; for(int i = 0; i < partitions.length; ++i) { partitions[i] = new TableCache(i, minPartitionCapacity, maxPartitionCapacity); } if(log.isTraceEnabled()) { log.trace("min-capacity=" + minCapacity + ", max-capacity=" + maxCapacity + ", partitions=" + partitionsTotal); } } public void stopService() { if(overager != null) { overager.stop(); } } public void initOverager(long period, long maxAge, String threadName) { final long periodMs = period * 1000; final long maxAgeMs = maxAge * 1000; overager = new Overager(maxAgeMs, periodMs); new Thread(overager, threadName).start(); } /** * @jmx.managed-operation */ public void registerListener(Cache.Listener listener) { if(log.isTraceEnabled()) { log.trace("registered listener for " + getServiceName()); } this.listener = listener; for(int i = 0; i < partitions.length; ++i) { partitions[i].registerListener(listener); } } /** * @jmx.managed-operation */ public int size() { int size = 0; for(int i = 0; i < partitions.length; ++i) { size += partitions[i].size(); } return size; } /** * @jmx.managed-attribute */ public int getMaxCapacity() { return maxCapacity; } /** * @jmx.managed-attribute */ public void setMaxCapacity(int maxCapacity) { this.maxCapacity = maxCapacity; this.maxPartitionCapacity = maxCapacity / partitions.length + 1; for(int i = 0; i < partitions.length; ++i) { partitions[i].setMaxCapacity(maxPartitionCapacity); } } /** * @jmx.managed-attribute */ public int getMinCapacity() { return minCapacity; } /** * @jmx.managed-attribute */ public int getPartitionsTotal() { return partitions.length; } /** * @jmx.managed-attribute */ public int getMinPartitionCapacity() { return minPartitionCapacity; } /** * @jmx.managed-attribute */ public int getMaxPartitionCapacity() { return maxPartitionCapacity; } public void lock() { } public void lock(Object key) { int partitionIndex = getPartitionIndex(key); partitions[partitionIndex].lock(key); } public void unlock() { } public void unlock(Object key) { int partitionIndex = getPartitionIndex(key); partitions[partitionIndex].unlock(key); } public Object[] getFields(Object pk) { final int i = getPartitionIndex(pk); return partitions[i].getFields(pk); } public Object[] getRelations(Object pk) { final int i = getPartitionIndex(pk); return partitions[i].getRelations(pk); } public void put(Transaction tx, Object pk, Object[] fields, Object[] relations) { final int i = getPartitionIndex(pk); partitions[i].put(tx, pk, fields, relations); } public void remove(Transaction tx, Object pk) { final int i = getPartitionIndex(pk); partitions[i].remove(tx, pk); } public boolean contains(Transaction tx, Object pk) { final int i = getPartitionIndex(pk); return partitions[i].contains(tx, pk); } public void lockForUpdate(Transaction tx, Object pk) throws Exception { final int i = getPartitionIndex(pk); partitions[i].lockForUpdate(tx, pk); } public void releaseLock(Transaction tx, Object pk) throws Exception { final int i = getPartitionIndex(pk); partitions[i].releaseLock(tx, pk); } public void flush() { for(int i = 0; i < partitions.length; ++i) { final TableCache partition = partitions[i]; partition.lock(); try { partition.flush(); } finally { partition.unlock(); } } } // Private private int getPartitionIndex(Object key) { int hash = key.hashCode(); // make it positive if(hash < 0) { hash = hash == Integer.MIN_VALUE ? Integer.MAX_VALUE : -hash; } return hash % partitions.length; } // Inner private class Overager implements Runnable { private final long maxAgeMs; private final long periodMs; private boolean run = true; public Overager(long maxAgeMs, long periodMs) { this.maxAgeMs = maxAgeMs; this.periodMs = periodMs; } public void stop() { run = false; } public void run() { while(run) { long lastUpdated = System.currentTimeMillis() - maxAgeMs; for(int i = 0; i < partitions.length; ++i) { partitions[i].ageOut(lastUpdated); } try { Thread.sleep(periodMs); } catch(InterruptedException e) { } } } } }