/*
* 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 org.apache.ignite.internal.visor.query;
import java.math.BigDecimal;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ConcurrentMap;
import javax.cache.Cache;
import org.apache.ignite.binary.BinaryObject;
import org.apache.ignite.binary.BinaryObjectException;
import org.apache.ignite.binary.BinaryType;
import org.apache.ignite.internal.IgniteEx;
import org.apache.ignite.internal.binary.BinaryObjectEx;
import org.apache.ignite.internal.processors.timeout.GridTimeoutObjectAdapter;
import org.apache.ignite.internal.util.IgniteUtils;
import org.apache.ignite.internal.util.typedef.internal.S;
import org.apache.ignite.internal.util.typedef.internal.SB;
/**
* Contains utility methods for Visor query tasks and jobs.
*/
public class VisorQueryUtils {
/** How long to store future with query in node local map: 5 minutes. */
public static final Integer RMV_DELAY = 5 * 60 * 1000;
/** Prefix for node local key for SQL queries. */
public static final String SQL_QRY_NAME = "VISOR_SQL_QUERY";
/** Prefix for node local key for SCAN queries. */
public static final String SCAN_QRY_NAME = "VISOR_SCAN_QUERY";
/** Columns for SCAN queries. */
public static final List<VisorQueryField> SCAN_COL_NAMES = Arrays.asList(
new VisorQueryField(null, null, "Key Class", ""), new VisorQueryField(null, null, "Key", ""),
new VisorQueryField(null, null, "Value Class", ""), new VisorQueryField(null, null, "Value", "")
);
/**
* @param o - Object.
* @return String representation of object class.
*/
private static String typeOf(Object o) {
if (o != null) {
Class<?> clazz = o.getClass();
return clazz.isArray() ? IgniteUtils.compact(clazz.getComponentType().getName()) + "[]"
: IgniteUtils.compact(o.getClass().getName());
}
else
return "n/a";
}
/**
* @param o Object.
* @return String representation of value.
*/
private static String valueOf(Object o) {
if (o == null)
return "null";
if (o instanceof byte[])
return "size=" + ((byte[])o).length;
if (o instanceof Byte[])
return "size=" + ((Byte[])o).length;
if (o instanceof Object[])
return "size=" + ((Object[])o).length + ", values=[" + mkString((Object[])o, 120) + "]";
if (o instanceof BinaryObject)
return binaryToString((BinaryObject)o);
return o.toString();
}
/**
* @param arr Object array.
* @param maxSz Maximum string size.
* @return Fixed size string.
*/
private static String mkString(Object[] arr, int maxSz) {
String sep = ", ";
StringBuilder sb = new StringBuilder();
boolean first = true;
for (Object v : arr) {
if (first)
first = false;
else
sb.append(sep);
sb.append(v);
if (sb.length() > maxSz)
break;
}
if (sb.length() >= maxSz) {
String end = "...";
sb.setLength(maxSz - end.length());
sb.append(end);
}
return sb.toString();
}
/**
* Fetch rows from SCAN query future.
*
* @param cur Query future to fetch rows from.
* @param pageSize Number of rows to fetch.
* @return Fetched rows.
*/
public static List<Object[]> fetchScanQueryRows(VisorQueryCursor<Cache.Entry<Object, Object>> cur, int pageSize) {
List<Object[]> rows = new ArrayList<>();
int cnt = 0;
while (cur.hasNext() && cnt < pageSize) {
Cache.Entry<Object, Object> next = cur.next();
Object k = next.getKey();
Object v = next.getValue();
rows.add(new Object[] {typeOf(k), valueOf(k), typeOf(v), valueOf(v)});
cnt++;
}
return rows;
}
/**
* Checks is given object is one of known types.
*
* @param obj Object instance to check.
* @return {@code true} if it is one of known types.
*/
private static boolean isKnownType(Object obj) {
return obj instanceof String ||
obj instanceof Boolean ||
obj instanceof Byte ||
obj instanceof Integer ||
obj instanceof Long ||
obj instanceof Short ||
obj instanceof Date ||
obj instanceof Double ||
obj instanceof Float ||
obj instanceof BigDecimal ||
obj instanceof URL;
}
/**
* Convert Binary object to string.
*
* @param obj Binary object.
* @return String representation of Binary object.
*/
public static String binaryToString(BinaryObject obj) {
int hash = obj.hashCode();
if (obj instanceof BinaryObjectEx) {
BinaryObjectEx objEx = (BinaryObjectEx)obj;
BinaryType meta;
try {
meta = ((BinaryObjectEx)obj).rawType();
}
catch (BinaryObjectException ignore) {
meta = null;
}
if (meta != null) {
SB buf = new SB(meta.typeName());
if (meta.fieldNames() != null) {
buf.a(" [hash=").a(hash);
for (String name : meta.fieldNames()) {
Object val = objEx.field(name);
buf.a(", ").a(name).a('=').a(val);
}
buf.a(']');
return buf.toString();
}
}
}
return S.toString(obj.getClass().getSimpleName(),
"hash", hash, false,
"typeId", obj.type().typeId(), true);
}
/**
* Collects rows from sql query future, first time creates meta and column names arrays.
*
* @param cur Query cursor to fetch rows from.
* @param pageSize Number of rows to fetch.
* @return Fetched rows.
*/
public static List<Object[]> fetchSqlQueryRows(VisorQueryCursor<List<?>> cur, int pageSize) {
List<Object[]> rows = new ArrayList<>();
int cnt = 0;
while (cur.hasNext() && cnt < pageSize) {
List<?> next = cur.next();
int sz = next.size();
Object[] row = new Object[sz];
for (int i = 0; i < sz; i++) {
Object o = next.get(i);
if (o == null)
row[i] = null;
else if (isKnownType(o))
row[i] = o;
else if (o instanceof BinaryObject)
row[i] = binaryToString((BinaryObject)o);
else
row[i] = o.getClass().isArray() ? "binary" : o.toString();
}
rows.add(row);
cnt++;
}
return rows;
}
/**
* @param qryId Unique query result id.
*/
public static void scheduleResultSetHolderRemoval(final String qryId, final IgniteEx ignite) {
ignite.context().timeout().addTimeoutObject(new GridTimeoutObjectAdapter(RMV_DELAY) {
@Override public void onTimeout() {
ConcurrentMap<String, VisorQueryCursor> storage = ignite.cluster().nodeLocalMap();
VisorQueryCursor cur = storage.get(qryId);
if (cur != null) {
// If cursor was accessed since last scheduling, set access flag to false and reschedule.
if (cur.accessed()) {
cur.accessed(false);
scheduleResultSetHolderRemoval(qryId, ignite);
}
else {
// Remove stored cursor otherwise.
storage.remove(qryId);
cur.close();
}
}
}
});
}
}