/* * � Copyright IBM Corp. 2010 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * implied. See the License for the specific language governing * permissions and limitations under the License. */ package com.ibm.xsp.extlib.model; import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import com.ibm.commons.util.StringUtil; import com.ibm.xsp.FacesExceptionEx; import com.ibm.xsp.context.FacesContextEx; import com.ibm.xsp.extlib.model.DataAccessorSource.Container; /** * Object that encapsulates data access. * <p> * This object is specifically designed to work with an extended iterator data model.<br/> * A DataAccessor has 3 modes of working: * <ul> * <li>Live access to data (just using count and get) * <li>Live access to data block - one block is retrieved at a time * <li>Cached access to data, until the cache fills up... * </ul> * </p> * @author Philippe Riand */ public abstract class DataBlockAccessor extends DataAccessor implements Externalizable { private static final long serialVersionUID = 1L; public static abstract class Block implements Externalizable { private static final long serialVersionUID = 1L; int generationId; Block prev; Block next; int index; long creationTime; public Block() {} // Serialization public Block(int index) { this.index = index; this.creationTime = System.currentTimeMillis(); } public abstract int getLength(); public abstract Object getData(int index); public boolean isExpired(int timeout) { if(timeout>0) { long now = System.currentTimeMillis(); return now>(creationTime+(((long)timeout)*1000L)); } return false; } public void writeExternal(ObjectOutput out) throws IOException { out.writeInt(index); out.writeLong(creationTime); } public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { index = in.readInt(); creationTime = in.readLong(); } } public static class ArrayBlock extends Block { private static final long serialVersionUID = 1L; Object[] data; public ArrayBlock() {} // Serialization public ArrayBlock(int index, Object[] data) { super(index); this.data = data; } @Override public int getLength() { return data!=null ? data.length : 0; } @Override public Object getData(int index) { return data[index]; } @Override public void writeExternal(ObjectOutput out) throws IOException { super.writeExternal(out); out.writeObject(data); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { super.readExternal(in); data = (Object[])in.readObject(); } } public static class EmptyBlock extends Block { private static final long serialVersionUID = 1L; public EmptyBlock() {} // Serialization public EmptyBlock(int index) { super(index); } @Override public boolean isExpired(int timeout) { return false; } @Override public int getLength() { return 0; } @Override public Object getData(int index) { return null; } } private Block firstBlock; private int blockCount; private int blockSize; private int maxBlockCount; private int maxCount; private int totalCount; private int timeout; private transient boolean shouldRecomputeTotal; public DataBlockAccessor() { } public DataBlockAccessor(DataAccessorBlockSource dataSource, int maxBlockCount) { super(dataSource); this.blockSize = 20; // Arbitrary first value this.maxCount = -1; this.totalCount = -1; this.timeout = dataSource.getTimeout(); this.maxBlockCount = maxBlockCount; if(maxBlockCount<=0) { this.maxBlockCount = getDefaultMaxBlockCount(); } } protected int getMaxBlockCount() { return maxBlockCount; } protected int getDefaultMaxBlockCount() { return 0; } public int getMaxCount() { return maxCount; } public void setMaxCount(int maxCount) { this.maxCount = maxCount; } public int getTotalCount() { return totalCount; } public void setTotalCount(int totalCount) { this.totalCount = totalCount; } public int getTimeout() { return timeout; } public void setTimeout(int timeout) { this.timeout = timeout; } ////////////////////////////////////////////////////////////////////// // Required methods ////////////////////////////////////////////////////////////////////// // // Data access // @Override public int getCount() { if(totalCount>=0) { return totalCount; } if(!shouldRecomputeTotal) { if(maxCount>=0) { return maxCount; } } return -1; } @Override public Object get(int index) { Block b = findBlockForRow(index, true); if(b!=null) { int idx = index-(index/blockSize)*blockSize; if(idx<b.getLength()) { return b.getData(idx); } } return null; // Not loaded... } ////////////////////////////////////////////////////////////////////// // Serialization ////////////////////////////////////////////////////////////////////// public void writeExternal(ObjectOutput out) throws IOException { out.writeInt(blockCount); out.writeInt(blockSize); out.writeInt(maxBlockCount); out.writeInt(maxCount); out.writeInt(totalCount); out.writeInt(timeout); for(Block b=firstBlock; b!=null; b=b.next) { out.writeObject(b); } } public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { blockCount = in.readInt(); blockSize = in.readInt(); maxBlockCount = in.readInt(); maxCount = in.readInt(); totalCount = in.readInt(); timeout = in.readInt(); Block lastBlock = firstBlock = null; for(int i=0; i<blockCount; i++) { Block b = (Block)in.readObject(); if(firstBlock==null) { firstBlock = b; } else { lastBlock.next = b; b.prev = lastBlock; } lastBlock = b; blockLoaded(b); } } protected void blockLoaded(Block b) { } ////////////////////////////////////////////////////////////////////// // Optional methods ////////////////////////////////////////////////////////////////////// @Override public boolean handlePrefetch() { return true; } @Override public boolean isPrefetched(int index) { Block b = findBlockForRow(index,false); return b!=null; } @Override public void prefetchData(int start, int count) { // We should discard the current cache if the caller asks for a different block count // start should generally be a multiple of count... if(count!=blockSize && count>0) { clearData(true); blockSize = count; } if(blockSize == 0){ ArithmeticException ae = new ArithmeticException("Divide by zero error");// $NLX-DataBlockAccessor_DivideByZeroError-1$ String zeroArgConstr = "com.ibm.xsp.extlib.model.DataBlockAccessor.DataBlockAccessor()"; //$NON-NLS-1$ String twoArgConstr = "com.ibm.xsp.extlib.model.DataBlockAccessor.DataBlockAccessor(DataAccessorSource, int)"; //$NON-NLS-1$ String exMsg = "Blocksize cannot be zero. The {0} zero argument constructor should only be used during serialization. Please use the constructor {1}."; // $NLX-DataBlockAccessor_BlocksizeZeroSoWrongConstructor-1$ throw new FacesExceptionEx(ae, StringUtil.format(exMsg, zeroArgConstr, twoArgConstr)); } // Now, find the block and load it if necessary // Note that pre-fetch is just an indicator here, so 'count' might not be fulfilled findBlockByIndex(start/blockSize, true); } protected Block findBlockForRow(int rowIndex, boolean load) { return findBlockByIndex(rowIndex/blockSize,load); } protected Block findBlockByIndex(int blockIndex, boolean load) { FacesContextEx ctx = FacesContextEx.getCurrentInstance(); this.shouldRecomputeTotal = false; // Check the first block, as we don't have to move it around if(firstBlock!=null) { // Look for the other ones for(Block b=firstBlock; b!=null; b=b.next) { if(b.index==blockIndex) { // If it is expired, we discard it but only: // - During the rendering phase // - If it hasn't been already read in this phase (based on the rendering sequence if(b.isExpired(getTimeout())) { if(ctx.isRenderingPhase()) { Container dc = getDataSource().getDataContainer(ctx); if(dc!=null && b.generationId!=dc.getGenerationId()) { if(b.prev!=null) { b.prev.next = b.next; } else { firstBlock = b.next; } if(b.next!=null) { b.next.prev = b.prev; } blockCount--; break; } } } // Else, make it the first the block if not already if(firstBlock!=b) { // Move it at the first place b.prev.next = b.next; if(b.next!=null) { b.next.prev = b.prev; } b.prev = null; b.next = firstBlock; firstBlock = b.next.prev = b; } return b; } } } // Ok, the block is not there, so we should load it if(load) { Block b = loadBlock(blockIndex,blockSize); if(b!=null) { // Store the rendering sequence if there is a timeout if(getTimeout()>0) { Container dc = getDataSource().getDataContainer(ctx); if(dc!=null) { b.generationId = dc.getGenerationId(); } b.creationTime = System.currentTimeMillis(); } // Put it at the first place b.prev = null; b.next = firstBlock; if(firstBlock!=null) { firstBlock.prev = b; } firstBlock = b; blockCount++; // And remove the last one, if there are too many if(blockCount>getMaxBlockCount()) { Block last = firstBlock.next ; if(last!=null) { while(last.next!=null) {last=last.next;} last.prev.next = null; blockCount--; } } blockLoaded(b); // Update the max count // Try to estimate the row count // totalCount: when we are sure about the count // maxCount: the maximum we saw so far + 1(so the navigator displays an extra page) if(totalCount<0) { int c = b.getLength(); if(c!=blockSize) { // The end had been reached, this is the maximum count... totalCount = blockIndex*blockSize+c; } else { // We add one, as we we might have another entry available maxCount = Math.max(maxCount, blockIndex*blockSize+c+1); } } return b; } } return null; } protected abstract Block loadBlock(int index, int blockSize); @Override public void clearData(boolean recomputeCount) { blockCount = 0; firstBlock = null; if(recomputeCount) { shouldRecomputeTotal = true; } } @Override public void updateCount() { shouldRecomputeTotal = true; } // // More rows management // This is an extended way for counting entries, when the exact number of rows // is not known, and the pager needs to display ... if more rows are available // @Override public boolean canHaveMoreRows() { return true; } @Override public int hasMoreRows(int maxCount) { if(totalCount>=0) { return totalCount; } return this.maxCount; // Ok, this only what we know so far } }