| /* |
| * 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 >= 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 < 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; |
| } |
| } |