/* * The contents of this file are subject to the Mozilla Public License * Version 1.1 (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.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. * * The Original Code is the Kowari Metadata Store. * * The Initial Developer of the Original Code is Andrew Newman * Copyright (C) 2005. All Rights Reserved. * * Contributor(s): N/A. * * [NOTE: The text of this Exhibit A may differ slightly from the text * of the notices in the Source Code files of the Original Code. You * should use the text of this Exhibit A rather than the text found in the * Original Code Source Code for Your Modifications.] * */ package org.mulgara.server.rmi; // Java 2 standard packages import java.util.*; import java.util.zip.*; import java.io.*; // Third party packages import org.apache.log4j.*; import org.mulgara.query.Answer; import org.mulgara.query.ArrayAnswer; import org.mulgara.query.TuplesException; import org.mulgara.query.Variable; import java.io.ObjectInput; import java.io.IOException; /** * Contains a serializable page of answers retrieved from an {@link org.mulgara.query.Answer} object. * * @author <a href="http://staff.pisoftware.com/pag">Paula Gearon</a> * * @created 2004-03-26 * * @version $Revision: 1.1 $ * * @modified $Date: 2005/01/27 11:21:21 $ * * @maintenanceAuthor $Author: newmana $ * * @company <A href="mailto:info@PIsoftware.com">Plugged In Software</A> * * @copyright © 2001-2003 <A href="http://www.PIsoftware.com/">Plugged In * Software Pty Ltd</A> * * @licence <a href="{@docRoot}/../../LICENCE">Mozilla Public License v1.1</a> */ public class IteratorPageImpl implements AnswerPage, Externalizable { /** * Allow newer compiled version of the stub to operate when changes * have not occurred with the class. * NOTE : update this serialVersionUID when a method or a public member is * deleted. */ static final long serialVersionUID = 2899965036062665141L; /** System property to set the level of compression to use */ static final String COMPRESSION_LEVEL = "mulgara.rmi.compression"; /** Size of compression buffer */ static final int SERVER_BUFFER_SIZE = 8192; /** Size of compression/decompression buffer */ static final int CLIENT_BUFFER_SIZE = 4096; /** * The logging category to log to. */ private final static Logger log = Logger.getLogger(IteratorPageImpl.class.getName()); /** The number of rows used in this page */ private int pageSize = 0; /** The data held in the page. */ private List<Object[]> rows; /** The current row number. */ private int currentRow; /** The Column names. */ private String[] columnNames; /** Efficiency flag to indicate if this is the last page buildable from an answer. */ private boolean lastPage; public IteratorPageImpl() throws TuplesException { // nothing } /** * Main Constructor. Copies rows from an Answer into internal serializable data. * @param answer The Answer to get data from. This Answer gets moved on by up to pageSize rows. * @param pageSize The number of rows to copy from answer up to the max number of available rows from answer. * @throws TuplesException Can be thrown while accessing the answer parameter. */ public IteratorPageImpl(Answer answer, int pageSize) throws TuplesException { rows = new ArrayList<Object[]>(pageSize); int width = answer.getNumberOfVariables(); // fill the data list with rows from the answer int r = 0; // not used for indexing, just counts the rows while (r < pageSize && answer.next()) { r++; Object[] row = new Object[width]; // fill a row with data from the current answer row for (int i = 0; i < width; i++) { row[i] = answer.getObject(i); // if the current object is a non-serializeable answer, then change it so it is if (row[i] instanceof Answer && !(row[i] instanceof ArrayAnswer)) { Answer ans = (Answer)row[i]; row[i] = new ArrayAnswer(ans); ans.close(); } } rows.add(row); } this.pageSize = r; this.lastPage = this.pageSize < pageSize; currentRow = -1; // Convert variables to column name array Variable[] variables = answer.getVariables(); columnNames = new String[variables.length]; for (int i = 0; i < variables.length; i++) { columnNames[i] = variables[i].getName(); } } /** * Constructs a page based on an Answer with a default number of rows. * @param answer The Answer to get data from. This Answer gets moved on by up to DEFAULT_PAGE_SIZE rows. * @throws TuplesException Can be thrown while accessing the answer parameter. */ public IteratorPageImpl(Answer answer) throws TuplesException { this(answer, Integer.getInteger(PAGE_SIZE_PROPERTY, DEFAULT_PAGE_SIZE).intValue()); } /** * Move the internal state of this object onto the next row in the answer. * @return True if there is a new row. False if there is no data left. */ public boolean nextInPage() { return ++currentRow < pageSize; } /** * Gets the data for a particular column from the current row. * @param column The column number of the data to retrieve. * @return The object found at the requested location. */ public Object getObjectFromPage(int column) { assert currentRow < pageSize && currentRow >= 0; return (rows.get(currentRow))[column]; } /** * Gets the data for a column of a particular name from the current row. * @param name The column name of the data to retrieve. * @return The object found at the requested location. * @throws TuplesException The name was not a valid column. */ public Object getObjectFromPage(String name) throws TuplesException { assert currentRow < pageSize && currentRow >= 0; for (int i = 0; i < columnNames.length; i++) { if (name.equals(columnNames[i])) return (rows.get(currentRow))[i]; } throw new TuplesException("Field not found: " + name); } /** * Retrieves the number of rows in this page. * @return The number of rows in the current page, this is less than or equal to the page size. */ public long getPageSize() { return pageSize; } /** * Resets the current row to the beginning. Hopefully not needed, but included for completeness. */ public void beforeFirstInPage() { currentRow = -1; } /** * Indicates that this is the last page constructable for the current answer. * * @return true if there are no more pages after this one. */ public boolean isLastPage() { return lastPage; } /** * Reads the entire page to the input stream, decompressing if requested. * * @param in The data stream to read from. */ public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { // read the flag to see if the remaining stream is compressed boolean compression = in.readBoolean(); // determine the uncompressed size of a compressed stream int uncompressedSize = 0; if (compression) { uncompressedSize = in.readInt(); } // read in the bytes which make up the rest of the object byte[] byteArray = (byte[])in.readObject(); // convert the bytes to a stream InputStream dataStream = new ByteArrayInputStream(byteArray); // extract the data if it was compressed if (compression) { // allocate a new array to hold uncompressed data byteArray = new byte[uncompressedSize]; int bytesRead = 0; // build the decompression object InflaterInputStream inflater = new InflaterInputStream(dataStream, new Inflater(), CLIENT_BUFFER_SIZE); // loop until the whole data stream is decompressed while (uncompressedSize - bytesRead > 0) { int l = inflater.read(byteArray, bytesRead, uncompressedSize - bytesRead); bytesRead += l; if (l < 0) { throw new IOException("Error in compressed data stream"); } } // convert the decompressed bytes to a stream dataStream = new ByteArrayInputStream(byteArray); } // build an object to extract objects from the stream ObjectInputStream data = new ObjectInputStream(dataStream); // The number of rows used in this page pageSize = data.readInt(); // The current row number currentRow = data.readInt(); // Efficiency flag to indicate if this is the last page buildable from an answer lastPage = data.readBoolean(); // the number of columns int size = data.readInt(); // The Column names columnNames = new String[size]; for (int i = 0; i < size; i++) { columnNames[i] = (String) data.readObject(); } // The data held in the page readRows(data); } /** This is a variable used for metrics when logging and debugging */ private static long serialTime = 0; /** * Writes the entire page to the output stream, compressing if requested. * * @param out The data stream to write to. */ public void writeExternal(ObjectOutput out) throws IOException { long currentTime = System.currentTimeMillis(); ByteArrayOutputStream data = new ByteArrayOutputStream(); ObjectOutputStream objectData = new ObjectOutputStream(data); // The number of rows used in this page objectData.writeInt(pageSize); // The current row number objectData.writeInt(currentRow); // Efficiency flag to indicate if this is the last page buildable from an answer objectData.writeBoolean(lastPage); // The number of column names objectData.writeInt(columnNames.length); // The Column names for (int i = 0; i < columnNames.length; i++) { objectData.writeObject(columnNames[i]); } // The data held in the page writeRows(objectData); objectData.close(); byte[] outputData = data.toByteArray(); // compress if needed outputData = setCompressionLevel(out, outputData); /** Write the object data out */ out.writeObject(outputData); long diffTime = System.currentTimeMillis() - currentTime; serialTime += diffTime; log.debug( "writeExternal - written : "+outputData.length+", down from: "+data.size()+ " ("+(100-(outputData.length*100/data.size()))+"%) : total time=" + serialTime ); } /** * Helper method for writeExternal to write the rows object. * * @param output The object output stream to write rows into. */ protected void writeRows(ObjectOutputStream output) throws IOException { // the number of rows try { output.writeInt(rows.size()); // The data in each row Iterator<Object[]> ri = rows.iterator(); while (ri.hasNext()) { Object[] line = ri.next(); // now write each element in the line for (int c = 0; c < columnNames.length; c++) { output.writeObject(line[c]); } } } catch (IOException ei) { log.warn("IOException thrown", ei); throw ei; } catch (Throwable t) { log.warn("Throwable thrown", t); throw new RuntimeException("Throwable thrown", t); } } /** * Helper method for readExternal to read the rows object. * * @param input The object input stream to read rows from. */ protected void readRows(ObjectInputStream input) throws IOException, ClassNotFoundException { // the number of rows int rowCount = input.readInt(); rows = new ArrayList<Object[]>(pageSize); // iterate over each row and add for (int r = 0; r < rowCount; r++) { // build up the contents of the row Object[] line = new Object[columnNames.length]; for (int c = 0; c < columnNames.length; c++) { line[c] = input.readObject(); } // add the row to the page rows.add(line); } } /** * Helper method for writeExternal to test if compression is needed, * and compresses the data if applicable. * * @param outStream The output stream to set the compression on. * @param data The data to compress for the stream. * @return The new data for the stream. */ protected byte[] setCompressionLevel(ObjectOutput outStream, byte[] data) throws IOException { // test if compression should be used int clevel = Integer.getInteger(COMPRESSION_LEVEL, 1).intValue(); if (clevel > 0) { if (clevel > Deflater.BEST_COMPRESSION) { clevel = Deflater.BEST_COMPRESSION; } // store the length of the buffer int datalength = data.length; // Create the compressed stream ByteArrayOutputStream zData = new ByteArrayOutputStream(); DeflaterOutputStream deflater = new DeflaterOutputStream(zData, new Deflater(clevel), SERVER_BUFFER_SIZE); // compress the data into zData deflater.write(data); deflater.close(); // set the byte array to the newly compressed data data = zData.toByteArray(); // write a flag to indicate that compression was used outStream.writeBoolean(true); // write the length of the compressed data outStream.writeInt(datalength); } else { // write a flag to indicate that compression was NOT used outStream.writeBoolean(false); } // return the data to be written to the stream return data; } }