/* * RED5 Open Source Flash Server - http://code.google.com/p/red5/ * * Copyright 2006-2012 by respective authors (see below). All rights reserved. * * 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 org.red5.io.object; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.red5.server.net.remoting.RemotingClient; /** * Read only RecordSet object that might be received through remoting calls. There are 3 types of data fetching: * * <ul> * <li>On demand (used by default)</li> * <li>Fetch all at once</li> * <li>Page-by-page fetching</li> * </ul> * * <p>For last mode, use <tt>page size</tt> property to specify maximum number of rows on one page</p> * * @author The Red5 Project (red5@osflash.org) * @author Joachim Bauch (jojo@struktur.de) * @see <a href="http://www.osflash.org/amf/recordset">osflash.org documentation</a> */ public class RecordSet { /** * On demand fetching mode */ private static final String MODE_ONDEMAND = "ondemand"; /** * Fetch all at once fetching mode */ private static final String MODE_FETCHALL = "fetchall"; /** * Page-by-page fetching mode */ private static final String MODE_PAGE = "page"; /** * Total number of pages */ private int totalCount; /** * Recordset data */ private List<List<Object>> data; /** * Recordset cursor */ private int cursor; /** * Name of service */ private String serviceName; /** * Recordset column names set */ private List<String> columns; /** * Recordset version */ private int version; /** * Recordset id */ private Object id; /** * Remoting client that fetches data */ private RemotingClient client; /** * Fetching mode, on demand by default */ private String mode = MODE_ONDEMAND; /** * Page size */ private int pageSize = 25; /** * Creates recordset from Input object * @param input input */ @SuppressWarnings({ "unchecked" }) public RecordSet(Input input) { // Create deserializer Deserializer deserializer = new Deserializer(); Map<String, Object> dataMap = input.readKeyValues(deserializer); Object map = dataMap.get("serverinfo"); Map<String, Object> serverInfo = null; if (map != null) { if (!(map instanceof Map)) { throw new RuntimeException("Expected Map but got " + map.getClass().getName()); } serverInfo = (Map<String, Object>) map; totalCount = (Integer) serverInfo.get("totalCount"); List<List<Object>> initialData = (List<List<Object>>) serverInfo.get("initialData"); cursor = (Integer) serverInfo.get("cursor"); serviceName = (String) serverInfo.get("serviceName"); columns = (List<String>) serverInfo.get("columnNames"); version = (Integer) serverInfo.get("version"); id = serverInfo.get("id"); this.data = new ArrayList<List<Object>>(totalCount); for (int i = 0; i < initialData.size(); i++) { this.data.add(i + cursor - 1, initialData.get(i)); } } else { throw new RuntimeException("Map (serverinfo) was null"); } } /** * Set the remoting client to use for retrieving of paged results. * * @param client Remoting client that works with this Recordset */ public void setRemotingClient(RemotingClient client) { this.client = client; } /** * Set the mode for fetching paged results. * * @param mode Mode for fetching of results */ public void setDeliveryMode(String mode) { setDeliveryMode(mode, 25, 0); } /** * Set the mode for fetching paged results with given max page size. * * @param mode Mode for fetching of results * @param pageSize Max page size */ public void setDeliveryMode(String mode, int pageSize) { setDeliveryMode(mode, pageSize, 0); } /** * Set the mode for fetching paged results with given max page size and number of prefetched pages. * * @param mode Mode for fetching of results * @param pageSize Max page size * @param prefetchCount Number of prefetched pages (not implemented yet) */ public void setDeliveryMode(String mode, int pageSize, int prefetchCount) { this.mode = mode; this.pageSize = pageSize; } /** * Return a list containing the names of the columns in the recordset. * * @return Column names set */ public List<String> getColumnNames() { return Collections.unmodifiableList(columns); } /** * Make sure the passed item has been fetched from the server. * * @param index Item index */ private void ensureAvailable(int index) { if (data.get(index) != null) { // Already have this item. return; } if (client == null) { throw new RuntimeException("no remoting client configured"); } Object result; int start = index; int count; if (mode.equals(MODE_ONDEMAND)) { // Only get requested item count = 1; } else if (mode.equals(MODE_FETCHALL)) { // Get remaining items count = totalCount - cursor; } else if (mode.equals(MODE_PAGE)) { // Get next page // TODO: implement prefetching of multiple pages count = 1; for (int i = 1; i < pageSize; i++) { if (this.data.get(start + i) == null) { count += 1; } } } else { // Default to "ondemand" count = 1; } result = client.invokeMethod(serviceName + ".getRecords", new Object[] { id, start + 1, count }); if (!(result instanceof RecordSetPage)) { throw new RuntimeException("expected RecordSetPage but got " + result); } RecordSetPage page = (RecordSetPage) result; if (page.getCursor() != start + 1) { throw new RuntimeException("expected offset " + (start + 1) + " but got " + page.getCursor()); } List<List<Object>> data = page.getData(); if (data.size() != count) { throw new RuntimeException("expected " + count + " results but got " + data.size()); } // Store received items for (int i = 0; i < count; i++) { this.data.add(start + i, data.get(i)); } } /** * Return a specified item from the recordset. If the item is not * available yet, it will be received from the server. * * @param index Item index * @return Item from recordset */ public List<Object> getItemAt(int index) { if (index < 0 || index >= totalCount) { // Out of range return null; } ensureAvailable(index); return data.get(index); } /** * Get the total number of items. * * @return Number of items */ public int getLength() { return totalCount; } /** * Get the number of items already received from the server. * * @return Nsumber of received items */ public int getNumberAvailable() { int result = 0; for (int i = 0; i < data.size(); i++) { if (data.get(i) != null) { result += 1; } } return result; } /** * Check if all items are available on the client. * * @return number of available items */ public boolean isFullyPopulated() { return getNumberAvailable() == getLength(); } /** * Return Map that can be serialized as result. * * @return serializable informations */ public Map<String, Object> serialize() { Map<String, Object> serverInfo = new HashMap<String, Object>(); serverInfo.put("totalCount", totalCount); serverInfo.put("cursor", cursor); serverInfo.put("serviceName", serviceName); serverInfo.put("columnNames", columns); serverInfo.put("version", version); serverInfo.put("id", id); serverInfo.put("initialData", data); return serverInfo; } }