blob: ec4a5aa509ba3c167ae8cca28dd0e993272b9256 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 flex.messaging.io;
import flex.messaging.util.UUIDUtils;
import javax.sql.RowSet;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* A wrapper for a RowSet to make it pageable. This technique is recommended
* if the RowSet is 'too big' to download all at once, AND the developer's
* client-side code is capable of dealing with non-fully-populated recordsets,
* that is, ActionScript RecordSets which are missing some of their data.
*
* @version 1.0
*/
public class PagedRowSet implements PageableRowSet {
private RowSet rowSet;
private String[] colNames;
private int pageSize = 50; //Default to 50 records a page
private int colCount = 0;
private int rowCount = 0;
private String id = null;
private String serviceName = null;
/**
* Pageable Rowset Service Name.
*/
public static final String DEFAULT_PAGING_SERVICE_NAME = "PageableRowSetCache";
/**
* Constructor
* <p>
* Creates a UUID for this object. Format: `XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'
* </p>
*
* @param r The RowSet to be paged.
* @param p The initial page size.
*/
public PagedRowSet(RowSet r, int p) {
serviceName = DEFAULT_PAGING_SERVICE_NAME;
rowSet = r;
pageSize = p;
id = UUIDUtils.createUUID();
init();
}
/**
* Allows the unique id generation of the RowSet to be toggled.
*
* @param r the row set
* @param p the page size
* @param createID should we create an id?
* @see #PagedRowSet(RowSet, int)
*/
public PagedRowSet(RowSet r, int p, boolean createID) {
serviceName = DEFAULT_PAGING_SERVICE_NAME;
rowSet = r;
pageSize = p;
if (createID) {
id = UUIDUtils.createUUID();
}
init();
}
private void init() {
if (rowSet != null) {
//Initialize columns
initColumns();
//Initialize records
initRecords();
} else {
colNames = new String[0];
}
}
private synchronized void initColumns() {
try {
ResultSetMetaData rsmd = rowSet.getMetaData();
if (rsmd != null) {
colCount = rsmd.getColumnCount();
}
} catch (SQLException ex) {
colCount = 0;
}
}
private synchronized void initRecords() {
//Determine rs size
if (rowSet != null) {
try {
int currentIndex = rowSet.getRow();
//Go to the end and get that row number
if (rowSet.last()) {
rowCount = rowSet.getRow();
}
//Put the cursor back
if (currentIndex > 0) {
rowSet.absolute(currentIndex);
} else {
rowSet.beforeFirst();
}
} catch (SQLException ex) {
//TODO: Decide whether if absolute() not be supported, try first() as a last resort??
try {
rowSet.first();
} catch (SQLException se) {
//we won't try anymore.
}
}
}
}
/**
* List the column names of the result set.
*
* @return String[] An array of the column names as strings, as ordered
* by the result set provider's column number assignment.
*/
public synchronized String[] getColumnNames() {
// Cache the column names lookup
if (colNames == null) {
try {
//Ensure column count is initialized
if (colCount == 0) {
initColumns();
}
colNames = new String[colCount];
for (int i = 0; i < colCount; i++) {
//Note: column numbers start at 1
colNames[i] = rowSet.getMetaData().getColumnName(i + 1);
}
} catch (SQLException ex) {
colNames = new String[0];
}
}
// Create a copy
String[] ret = new String[colNames.length];
System.arraycopy(colNames, 0, ret, 0, colNames.length);
return ret;
}
/**
* Use this method to get a map of the index used to start the data page,
* and an array of arrays of the actual data page itself.
*
* @param startIndex starting index
* @param count how many records to return
* @return Map A map with two fields, the index of the row to start the page, and an array of
* arrays for the actual data page.
* @throws SQLException if unable to get data from the rowset
*/
public synchronized Map getRecords(int startIndex, int count) throws SQLException {
List aRecords = new ArrayList(); //Don't initialize with count as it could be Integer.MAX_VALUE
//Ensure column count is initialized
if (colCount == 0) {
initColumns();
}
//Starting index cannot be less than 1
if (startIndex < 1)
startIndex = 1;
//Populate the page, moving cursor to index
if (rowSet.absolute(startIndex)) {
//Loop over the result set for the count specified
for (int i = 0; i < count; i++) {
boolean hasNext;
List row;
if (colCount > 0) {
row = new ArrayList(rowCount + 1);
//Loop over columns to create an array for the row
for (int j = 1; j <= colCount; j++) {
Object data = rowSet.getObject(j);
if (data instanceof Clob) {
Clob clob = (Clob) data;
row.add(clob.getSubString(0, (int) clob.length()));
} else if (data instanceof Blob) {
Blob blob = (Blob) data;
byte[] bytes = blob.getBytes(1, (int) blob.length());
row.add(bytes);
} else
row.add(data);
}
} else //HACK: Handle any ColdFusion Query Objects that have no column metadata!
{
row = new ArrayList();
try {
//Get as many columns as possible to build the row
//Stop on error or the first null column returned.
for (int j = 1; j <= 50; j++) {
Object o = rowSet.getObject(j);
if (o != null) {
row.add(o);
} else {
break;
}
}
} catch (SQLException ex) {
//Stop looking and just add the row.
}
}
aRecords.add(row.toArray());
hasNext = rowSet.next();
//Cursor beyond last row, stop!
if (!hasNext) {
break;
}
}
}
Map result = new HashMap(2);
result.put(PAGE, aRecords.toArray());
result.put(CURSOR, Integer.valueOf(startIndex));
return result;
}
/**
* Get the row count.
*
* @return int The total number of rows in the result set.
*/
public int getRowCount() {
return rowCount;
}
/**
* If this function returns a number &gt;= the total number of rows in the result set,
* then the result set should be simply returned to the client in full. However,
* if it is &lt; the total size, then this object itself is saved in Session data,
* and tagged with a unique ID.
*
* @return the page size
*/
public int getInitialDownloadCount() {
return pageSize;
}
/**
* Return the id of this row set.
*
* @return the id
*/
public String getID() {
return id;
}
/**
* Get the service name.
*
* @return String The name of the service that will manage this paged result.
*/
public String getServiceName() {
return serviceName;
}
/**
* Set the service name.
*
* @param serviceName Update the name of the service that manages the pages for this query.
*/
public void setServicename(String serviceName) {
this.serviceName = serviceName;
}
}