This is an automated email from the ASF dual-hosted git repository.
nizhikov pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git
The following commit(s) were added to refs/heads/master by this push:
new c7d2e6e6f12 IGNITE-22538 Customizable CacheScanTask output (#11400)
c7d2e6e6f12 is described below
commit c7d2e6e6f12990e0bcf2166bdef10f7a9de9c38f
Author: Nikolay <[email protected]>
AuthorDate: Thu Jun 20 18:09:02 2024 +0300
IGNITE-22538 Customizable CacheScanTask output (#11400)
---
.../util/GridCommandHandlerClusterByClassTest.java | 135 ++++++++++++++++++
...ernal.management.cache.scan.CacheScanTaskFormat | 2 +
.../internal/management/cache/CacheCommand.java | 1 +
.../cache/{ => scan}/CacheScanCommand.java | 2 +-
.../cache/{ => scan}/CacheScanCommandArg.java | 18 ++-
.../management/cache/{ => scan}/CacheScanTask.java | 135 ++++--------------
.../cache/scan/CacheScanTaskFormat.java} | 44 +++---
.../cache/{ => scan}/CacheScanTaskResult.java | 2 +-
.../cache/scan/DefaultCacheScanTaskFormat.java | 153 +++++++++++++++++++++
.../cache/scan/TableCacheScanTaskFormat.java | 102 ++++++++++++++
.../main/resources/META-INF/classnames.properties | 8 +-
.../org/apache/ignite/platform/model/Employee.java | 10 ++
.../apache/ignite/testframework/GridTestUtils.java | 24 ++++
...mandHandlerClusterByClassTest_cache_help.output | 3 +-
...dlerClusterByClassWithSSLTest_cache_help.output | 3 +-
15 files changed, 501 insertions(+), 141 deletions(-)
diff --git
a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerClusterByClassTest.java
b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerClusterByClassTest.java
index 3747d5aa98a..13d380ddc6c 100644
---
a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerClusterByClassTest.java
+++
b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerClusterByClassTest.java
@@ -24,7 +24,9 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Calendar;
import java.util.Collection;
+import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
@@ -80,6 +82,8 @@ import
org.apache.ignite.internal.management.cache.CacheClearCommand;
import org.apache.ignite.internal.management.cache.CacheCommand;
import org.apache.ignite.internal.management.cache.CacheDestroyCommand;
import org.apache.ignite.internal.management.cache.IdleVerifyDumpTask;
+import
org.apache.ignite.internal.management.cache.scan.DefaultCacheScanTaskFormat;
+import
org.apache.ignite.internal.management.cache.scan.TableCacheScanTaskFormat;
import org.apache.ignite.internal.management.tx.TxTaskResult;
import org.apache.ignite.internal.processors.cache.CacheGroupContext;
import org.apache.ignite.internal.processors.cache.CacheType;
@@ -96,6 +100,9 @@ import org.apache.ignite.internal.util.typedef.internal.S;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.apache.ignite.lang.IgniteInClosure;
import org.apache.ignite.logger.java.JavaLogger;
+import org.apache.ignite.platform.model.AccessLevel;
+import org.apache.ignite.platform.model.Employee;
+import org.apache.ignite.platform.model.Key;
import org.apache.ignite.testframework.junits.GridAbstractTest;
import org.apache.ignite.testframework.junits.WithSystemProperty;
import org.apache.ignite.transactions.Transaction;
@@ -187,6 +194,18 @@ public class GridCommandHandlerClusterByClassTest extends
GridCommandHandlerClus
/** */
public static final String SCAN = "scan";
+ /** */
+ public static final String JOHN = "John Connor";
+
+ /** */
+ public static final String SARAH = "Sarah Connor";
+
+ /** */
+ public static final String KYLE = "Kyle Reese";
+
+ /** */
+ public static final Date DATE = new Date(104, Calendar.JULY, 25);
+
/**
* Very basic tests for running the command in different environment which
other command are running in.
*/
@@ -1656,6 +1675,87 @@ public class GridCommandHandlerClusterByClassTest
extends GridCommandHandlerClus
assertNotContains(log, testOut.toString(), "Result limited");
}
+ /** */
+ private void dataForScanTest() {
+ IgniteCache<Key, Employee> c1 = crd.createCache(new
CacheConfiguration<>("cache1"));
+
+ c1.put(new Key(1), new Employee(JOHN, 2));
+ c1.put(new Key(2), new Employee(SARAH, 3));
+ c1.put(new Key(3), new Employee(KYLE, 4));
+
+ IgniteCache<Integer, String> c2 = crd.createCache(new
CacheConfiguration<>("cache2"));
+
+ c2.put(1, JOHN);
+ c2.put(2, SARAH);
+ c2.put(3, KYLE);
+
+ IgniteCache<Integer, TestClass2> c3 = crd.createCache(new
CacheConfiguration<>("cache3"));
+
+ c3.put(1, new TestClass2(
+ 1,
+ new int[]{2, 3},
+ Collections.singletonMap("some_key", "some_value"),
+ new String[] {"s1", "s2", "s3"},
+ DATE,
+ Arrays.asList(1, 2, 3),
+ AccessLevel.USER
+ ));
+
+ c3.put(2, new TestClass2(
+ 2,
+ new int[]{3, 4},
+ Collections.singletonMap("1", "2"),
+ new String[] {"s4", "s5", "s6"},
+ DATE,
+ Arrays.asList(1, 2, 3),
+ AccessLevel.USER
+ ));
+
+ c3.put(3, new TestClass2(
+ 3,
+ new int[]{4, 5},
+ Collections.singletonMap("xxx", "yyy"),
+ new String[] {"s7", "s8", "s9"},
+ DATE,
+ Arrays.asList(1, 2, 3),
+ AccessLevel.SUPER
+ ));
+ }
+
+ /** */
+ @Test
+ public void testCacheScanTableFormat() {
+ injectTestSystemOut();
+
+ autoConfirmation = false;
+
+ dataForScanTest();
+
+ assertEquals(EXIT_CODE_OK, execute("--cache", SCAN, "--output-format",
TableCacheScanTaskFormat.NAME, "cache1"));
+
+ assertContains(log, testOut.toString(), Pattern.compile("id *fio
*salary *\n"));
+ assertContains(log, testOut.toString(), Pattern.compile("1 *" + JOHN +
" *2 *\n"));
+ assertContains(log, testOut.toString(), Pattern.compile("2 *" + SARAH
+ " *3 *\n"));
+ assertContains(log, testOut.toString(), Pattern.compile("3 *" + KYLE +
" *4 *\n"));
+
+ assertEquals(EXIT_CODE_OK, execute("--cache", SCAN, "--output-format",
TableCacheScanTaskFormat.NAME, "cache2"));
+
+ assertContains(
+ log,
+ testOut.toString(),
+ Pattern.compile(DefaultCacheScanTaskFormat.KEY + " *" +
DefaultCacheScanTaskFormat.VALUE + " *\n")
+ );
+ assertContains(log, testOut.toString(), Pattern.compile("1 *" + JOHN +
" *\n"));
+ assertContains(log, testOut.toString(), Pattern.compile("2 *" + SARAH
+ " *\n"));
+ assertContains(log, testOut.toString(), Pattern.compile("3 *" + KYLE +
" *\n"));
+
+ assertEquals(EXIT_CODE_OK, execute("--cache", SCAN, "--output-format",
TableCacheScanTaskFormat.NAME, "cache3"));
+
+ assertContains(log, testOut.toString(), "some_key=some_value");
+ assertContains(log, testOut.toString(), "xxx=yyy");
+ assertContains(log, testOut.toString(), DATE.toString());
+ }
+
/** */
@Test
public void testCacheScanLimit() {
@@ -2272,4 +2372,39 @@ public class GridCommandHandlerClusterByClassTest
extends GridCommandHandlerClus
this.s = s;
}
}
+
+ /** */
+ private static class TestClass2 {
+ /** */
+ private final int i;
+
+ /** */
+ private final int[] ints;
+
+ /** */
+ private final Map<?, ?> map;
+
+ /** */
+ private final String[] strArr;
+
+ /** */
+ private final Date date;
+
+ /** */
+ private final List<?> list;
+
+ /** */
+ private final AccessLevel enm;
+
+ /** */
+ public TestClass2(int i, int[] ints, Map<?, ?> map, String[] strArr,
Date date, List<?> list, AccessLevel enm) {
+ this.i = i;
+ this.ints = ints;
+ this.map = map;
+ this.strArr = strArr;
+ this.date = date;
+ this.list = list;
+ this.enm = enm;
+ }
+ }
}
diff --git
a/modules/core/src/main/java/META-INF/services/org.apache.ignite.internal.management.cache.scan.CacheScanTaskFormat
b/modules/core/src/main/java/META-INF/services/org.apache.ignite.internal.management.cache.scan.CacheScanTaskFormat
new file mode 100644
index 00000000000..76b16f3df03
--- /dev/null
+++
b/modules/core/src/main/java/META-INF/services/org.apache.ignite.internal.management.cache.scan.CacheScanTaskFormat
@@ -0,0 +1,2 @@
+org.apache.ignite.internal.management.cache.scan.DefaultCacheScanTaskFormat
+org.apache.ignite.internal.management.cache.scan.TableCacheScanTaskFormat
diff --git
a/modules/core/src/main/java/org/apache/ignite/internal/management/cache/CacheCommand.java
b/modules/core/src/main/java/org/apache/ignite/internal/management/cache/CacheCommand.java
index 5720f8d19e5..66517db7c60 100644
---
a/modules/core/src/main/java/org/apache/ignite/internal/management/cache/CacheCommand.java
+++
b/modules/core/src/main/java/org/apache/ignite/internal/management/cache/CacheCommand.java
@@ -20,6 +20,7 @@ package org.apache.ignite.internal.management.cache;
import org.apache.ignite.internal.management.api.CommandRegistryImpl;
import org.apache.ignite.internal.management.api.HelpCommand;
import org.apache.ignite.internal.management.api.NoArg;
+import org.apache.ignite.internal.management.cache.scan.CacheScanCommand;
/** */
public class CacheCommand extends CommandRegistryImpl<NoArg, Void> {
diff --git
a/modules/core/src/main/java/org/apache/ignite/internal/management/cache/CacheScanCommand.java
b/modules/core/src/main/java/org/apache/ignite/internal/management/cache/scan/CacheScanCommand.java
similarity index 97%
rename from
modules/core/src/main/java/org/apache/ignite/internal/management/cache/CacheScanCommand.java
rename to
modules/core/src/main/java/org/apache/ignite/internal/management/cache/scan/CacheScanCommand.java
index 7ccf9e790dd..13022685194 100644
---
a/modules/core/src/main/java/org/apache/ignite/internal/management/cache/CacheScanCommand.java
+++
b/modules/core/src/main/java/org/apache/ignite/internal/management/cache/scan/CacheScanCommand.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package org.apache.ignite.internal.management.cache;
+package org.apache.ignite.internal.management.cache.scan;
import java.util.List;
import java.util.function.Consumer;
diff --git
a/modules/core/src/main/java/org/apache/ignite/internal/management/cache/CacheScanCommandArg.java
b/modules/core/src/main/java/org/apache/ignite/internal/management/cache/scan/CacheScanCommandArg.java
similarity index 82%
rename from
modules/core/src/main/java/org/apache/ignite/internal/management/cache/CacheScanCommandArg.java
rename to
modules/core/src/main/java/org/apache/ignite/internal/management/cache/scan/CacheScanCommandArg.java
index d39df18f14f..e1278c95e5b 100644
---
a/modules/core/src/main/java/org/apache/ignite/internal/management/cache/CacheScanCommandArg.java
+++
b/modules/core/src/main/java/org/apache/ignite/internal/management/cache/scan/CacheScanCommandArg.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package org.apache.ignite.internal.management.cache;
+package org.apache.ignite.internal.management.cache.scan;
import java.io.IOException;
import java.io.ObjectInput;
@@ -38,6 +38,10 @@ public class CacheScanCommandArg extends
IgniteDataTransferObject {
@Argument(example = "cacheName")
private String cacheName;
+ /** */
+ @Argument(description = "Pluggable output format. 'default', 'table'
exists by default", example = "table", optional = true)
+ private String outputFormat;
+
/** */
@Argument(description = "limit count of entries to scan (" + DFLT_LIMIT +
" by default)", example = "N", optional = true)
private int limit = DFLT_LIMIT;
@@ -45,15 +49,27 @@ public class CacheScanCommandArg extends
IgniteDataTransferObject {
/** {@inheritDoc} */
@Override protected void writeExternalData(ObjectOutput out) throws
IOException {
U.writeString(out, cacheName);
+ U.writeString(out, outputFormat);
out.writeInt(limit);
}
/** {@inheritDoc} */
@Override protected void readExternalData(byte protoVer, ObjectInput in)
throws IOException, ClassNotFoundException {
cacheName = U.readString(in);
+ outputFormat = U.readString(in);
limit = in.readInt();
}
+ /** */
+ public String outputFormat() {
+ return outputFormat;
+ }
+
+ /** */
+ public void outputFormat(String format) {
+ this.outputFormat = format;
+ }
+
/** */
public String cacheName() {
return cacheName;
diff --git
a/modules/core/src/main/java/org/apache/ignite/internal/management/cache/CacheScanTask.java
b/modules/core/src/main/java/org/apache/ignite/internal/management/cache/scan/CacheScanTask.java
similarity index 51%
rename from
modules/core/src/main/java/org/apache/ignite/internal/management/cache/CacheScanTask.java
rename to
modules/core/src/main/java/org/apache/ignite/internal/management/cache/scan/CacheScanTask.java
index 646693ed92e..ca8b5ba8dda 100644
---
a/modules/core/src/main/java/org/apache/ignite/internal/management/cache/CacheScanTask.java
+++
b/modules/core/src/main/java/org/apache/ignite/internal/management/cache/scan/CacheScanTask.java
@@ -15,25 +15,20 @@
* limitations under the License.
*/
-package org.apache.ignite.internal.management.cache;
+package org.apache.ignite.internal.management.cache.scan;
import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import javax.cache.Cache;
import org.apache.ignite.IgniteCache;
-import org.apache.ignite.binary.BinaryObject;
-import org.apache.ignite.binary.BinaryObjectException;
-import org.apache.ignite.binary.BinaryType;
import org.apache.ignite.cache.query.QueryCursor;
import org.apache.ignite.cache.query.ScanQuery;
-import org.apache.ignite.internal.binary.BinaryObjectEx;
import org.apache.ignite.internal.processors.task.GridInternal;
-import org.apache.ignite.internal.util.IgniteUtils;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.util.typedef.internal.S;
-import org.apache.ignite.internal.util.typedef.internal.SB;
+import org.apache.ignite.internal.util.typedef.internal.U;
import org.apache.ignite.internal.visor.VisorJob;
import org.apache.ignite.internal.visor.VisorOneNodeTask;
import org.apache.ignite.plugin.security.SecurityPermissionSet;
@@ -82,23 +77,28 @@ public class CacheScanTask extends
VisorOneNodeTask<CacheScanCommandArg, CacheSc
IgniteCache<Object, Object> cache =
ignite.cache(arg.cacheName()).withKeepBinary();
- List<String> titles = Arrays.asList("Key Class", "Key", "Value
Class", "Value");
-
int cnt = 0;
List<List<?>> entries = new ArrayList<>();
+ CacheScanTaskFormat format = format(arg.outputFormat());
+
+ List<String> titles = Collections.emptyList();
+
ScanQuery<Object, Object> scanQry = new
ScanQuery<>().setPageSize(min(arg.limit(), DFLT_PAGE_SIZE));
try (QueryCursor<Cache.Entry<Object, Object>> qry =
cache.query(scanQry)) {
Iterator<Cache.Entry<Object, Object>> iter = qry.iterator();
- while (cnt++ < arg.limit() && iter.hasNext()) {
- Cache.Entry<Object, Object> next = iter.next();
+ if (cnt++ < arg.limit() && iter.hasNext()) {
+ Cache.Entry<Object, Object> first = iter.next();
+
+ titles = format.titles(first);
- Object k = next.getKey();
- Object v = next.getValue();
+ entries.add(format.row(first));
+ }
- entries.add(Arrays.asList(typeOf(k), valueOf(k),
typeOf(v), valueOf(v)));
+ while (cnt++ < arg.limit() && iter.hasNext()) {
+ entries.add(format.row(iter.next()));
}
}
@@ -112,103 +112,24 @@ public class CacheScanTask extends
VisorOneNodeTask<CacheScanCommandArg, CacheSc
return NO_PERMISSIONS;
}
- /**
- * @param o Source 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=[" + S.joinToString(Arrays.asList((Object[])o),
", ", "...", 120, 0) + "]";
- }
-
- if (o instanceof BinaryObject)
- return binaryToString((BinaryObject)o);
-
- return o.toString();
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return S.toString(CacheScanJob.class, this);
}
+ }
- /**
- * 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) {
- if (meta.isEnum()) {
- try {
- return obj.deserialize().toString();
- }
- catch (BinaryObjectException ignore) {
- // NO-op.
- }
- }
-
- 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);
- }
+ /** */
+ private static CacheScanTaskFormat format(String format) {
+ if (format == null)
+ return new DefaultCacheScanTaskFormat();
- buf.a(']');
+ Iterable<CacheScanTaskFormat> formats =
U.loadService(CacheScanTaskFormat.class);
- return buf.toString();
- }
- }
- }
-
- return S.toString(obj.getClass().getSimpleName(),
- "hash", hash, false,
- "typeId", obj.type().typeId(), true);
+ for (CacheScanTaskFormat f : formats) {
+ if (f.name().equals(format))
+ return f;
}
- /** {@inheritDoc} */
- @Override public String toString() {
- return S.toString(CacheScanJob.class, this);
- }
+ throw new IllegalStateException("Unknown format: " + format);
}
}
diff --git
a/modules/core/src/test/java/org/apache/ignite/platform/model/Employee.java
b/modules/core/src/main/java/org/apache/ignite/internal/management/cache/scan/CacheScanTaskFormat.java
similarity index 57%
copy from
modules/core/src/test/java/org/apache/ignite/platform/model/Employee.java
copy to
modules/core/src/main/java/org/apache/ignite/internal/management/cache/scan/CacheScanTaskFormat.java
index 6c63c29d684..a4e4ed8f02f 100644
--- a/modules/core/src/test/java/org/apache/ignite/platform/model/Employee.java
+++
b/modules/core/src/main/java/org/apache/ignite/internal/management/cache/scan/CacheScanTaskFormat.java
@@ -15,33 +15,27 @@
* limitations under the License.
*/
-package org.apache.ignite.platform.model;
+package org.apache.ignite.internal.management.cache.scan;
-/** Test value object. */
-public class Employee {
- /** */
- private String fio;
+import java.util.List;
+import javax.cache.Cache;
- /** */
- private long salary;
-
- /** */
- public String getFio() {
- return fio;
- }
-
- /** */
- public void setFio(String fio) {
- this.fio = fio;
- }
+/**
+ * This is pluggable interface to customize string representation of cache
data.
+ */
+public interface CacheScanTaskFormat {
+ /**
+ * @return name of the format.
+ * @see CacheScanCommandArg
+ */
+ String name();
- /** */
- public long getSalary() {
- return salary;
- }
+ /**
+ * Calculates and returns titles based on first cache entry.
+ * @return Column titles.
+ */
+ List<String> titles(Cache.Entry<Object, Object> first);
- /** */
- public void setSalary(long salary) {
- this.salary = salary;
- }
+ /** Row for single cache entry. */
+ List<?> row(Cache.Entry<Object, Object> e);
}
diff --git
a/modules/core/src/main/java/org/apache/ignite/internal/management/cache/CacheScanTaskResult.java
b/modules/core/src/main/java/org/apache/ignite/internal/management/cache/scan/CacheScanTaskResult.java
similarity index 97%
rename from
modules/core/src/main/java/org/apache/ignite/internal/management/cache/CacheScanTaskResult.java
rename to
modules/core/src/main/java/org/apache/ignite/internal/management/cache/scan/CacheScanTaskResult.java
index 9a3a8b41354..29abbf1bbb0 100644
---
a/modules/core/src/main/java/org/apache/ignite/internal/management/cache/CacheScanTaskResult.java
+++
b/modules/core/src/main/java/org/apache/ignite/internal/management/cache/scan/CacheScanTaskResult.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package org.apache.ignite.internal.management.cache;
+package org.apache.ignite.internal.management.cache.scan;
import java.io.IOException;
import java.io.ObjectInput;
diff --git
a/modules/core/src/main/java/org/apache/ignite/internal/management/cache/scan/DefaultCacheScanTaskFormat.java
b/modules/core/src/main/java/org/apache/ignite/internal/management/cache/scan/DefaultCacheScanTaskFormat.java
new file mode 100644
index 00000000000..f6aa82b0fa8
--- /dev/null
+++
b/modules/core/src/main/java/org/apache/ignite/internal/management/cache/scan/DefaultCacheScanTaskFormat.java
@@ -0,0 +1,153 @@
+/*
+ * 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.management.cache.scan;
+
+import java.util.Arrays;
+import java.util.List;
+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.binary.BinaryObjectEx;
+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;
+
+/**
+ * Default cache scan task format.
+ */
+public class DefaultCacheScanTaskFormat implements CacheScanTaskFormat {
+ /** Key title. */
+ public static final String KEY = "Key";
+
+ /** Value title. */
+ public static final String VALUE = "Value";
+
+ /** {@inheritDoc} */
+ @Override public String name() {
+ return "default";
+ }
+
+ /** {@inheritDoc} */
+ @Override public List<String> titles(Cache.Entry<Object, Object> first) {
+ return Arrays.asList("Key Class", KEY, "Value Class", VALUE);
+ }
+
+ /** {@inheritDoc} */
+ @Override public List<?> row(Cache.Entry<Object, Object> e) {
+ Object k = e.getKey();
+ Object v = e.getValue();
+
+ return Arrays.asList(typeOf(k), valueOf(k), typeOf(v), valueOf(v));
+ }
+
+ /**
+ * @param o Source 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.
+ */
+ 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=[" + S.joinToString(Arrays.asList((Object[])o), ",
", "...", 120, 0) + "]";
+ }
+
+ if (o instanceof BinaryObject)
+ return binaryToString((BinaryObject)o);
+
+ return o.toString();
+ }
+
+ /**
+ * 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) {
+ if (meta.isEnum()) {
+ try {
+ return obj.deserialize().toString();
+ }
+ catch (BinaryObjectException ignore) {
+ // NO-op.
+ }
+ }
+
+ 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);
+ }
+
+}
diff --git
a/modules/core/src/main/java/org/apache/ignite/internal/management/cache/scan/TableCacheScanTaskFormat.java
b/modules/core/src/main/java/org/apache/ignite/internal/management/cache/scan/TableCacheScanTaskFormat.java
new file mode 100644
index 00000000000..bf754225f68
--- /dev/null
+++
b/modules/core/src/main/java/org/apache/ignite/internal/management/cache/scan/TableCacheScanTaskFormat.java
@@ -0,0 +1,102 @@
+/*
+ * 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.management.cache.scan;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import javax.cache.Cache;
+import org.apache.ignite.binary.BinaryObject;
+
+/**
+ * This format prints cache objects fields in table format.
+ */
+public class TableCacheScanTaskFormat implements CacheScanTaskFormat {
+ /** */
+ public static final String NAME = "table";
+
+ /** */
+ private List<String> keyTitles;
+
+ /** */
+ private List<String> valTitles;
+
+ /** {@inheritDoc} */
+ @Override public String name() {
+ return NAME;
+ }
+
+ /** {@inheritDoc} */
+ @Override public List<String> titles(Cache.Entry<Object, Object> first) {
+ keyTitles = titles(first.getKey(), DefaultCacheScanTaskFormat.KEY);
+ valTitles = titles(first.getValue(), DefaultCacheScanTaskFormat.VALUE);
+
+ List<String> res = new ArrayList<>(keyTitles.size() +
valTitles.size());
+
+ res.addAll(keyTitles);
+ res.addAll(valTitles);
+
+ return res;
+ }
+
+ /** {@inheritDoc} */
+ @Override public List<?> row(Cache.Entry<Object, Object> e) {
+ List<String> res = new ArrayList<>(keyTitles.size() +
valTitles.size());
+
+ res.addAll(columns(e.getKey(), keyTitles,
DefaultCacheScanTaskFormat.KEY));
+ res.addAll(columns(e.getValue(), valTitles,
DefaultCacheScanTaskFormat.VALUE));
+
+ return res;
+ }
+
+ /** */
+ private List<String> titles(Object o, String dflt) {
+ if (o instanceof BinaryObject) {
+ BinaryObject b = (BinaryObject)o;
+
+ List<String> flds = new ArrayList<>(b.type().fieldNames());
+
+ Collections.sort(flds);
+
+ return flds;
+ }
+
+ return Collections.singletonList(dflt);
+ }
+
+ /** */
+ private Collection<String> columns(Object o, List<String> titles, String
dfltTitle) {
+ if (titles.size() == 1 && titles.get(0).equals(dfltTitle))
+ return
Collections.singletonList(DefaultCacheScanTaskFormat.valueOf(o));
+
+ String[] res = new String[titles.size()];
+
+ if (o instanceof BinaryObject) {
+ BinaryObject b = (BinaryObject)o;
+
+ for (int i = 0; i < titles.size(); i++) {
+ if (b.hasField(titles.get(i)))
+ res[i] =
DefaultCacheScanTaskFormat.valueOf(b.field(titles.get(i)));
+ }
+ }
+
+ return Arrays.asList(res);
+ }
+}
diff --git a/modules/core/src/main/resources/META-INF/classnames.properties
b/modules/core/src/main/resources/META-INF/classnames.properties
index e4544198795..7a45d5841c8 100644
--- a/modules/core/src/main/resources/META-INF/classnames.properties
+++ b/modules/core/src/main/resources/META-INF/classnames.properties
@@ -441,7 +441,7 @@
org.apache.ignite.internal.management.cache.CacheListCommand$OutputFormat
org.apache.ignite.internal.management.cache.CacheListCommandArg
org.apache.ignite.internal.management.cache.CacheMetricsCommandArg
org.apache.ignite.internal.management.cache.CacheResetLostPartitionsCommandArg
-org.apache.ignite.internal.management.cache.CacheScanCommandArg
+org.apache.ignite.internal.management.cache.scan.CacheScanCommandArg
org.apache.ignite.internal.management.cache.CacheScheduleIndexesRebuildCommandArg
org.apache.ignite.internal.management.cache.CacheValidateIndexesCommandArg
org.apache.ignite.internal.management.cdc.CdcDeleteLostSegmentLinksCommandArg
@@ -2095,9 +2095,9 @@ org.apache.ignite.internal.management.cache.CacheJdbcType
org.apache.ignite.internal.management.cache.CacheJdbcTypeField
org.apache.ignite.internal.management.cache.CacheNearConfiguration
org.apache.ignite.internal.management.cache.CacheRebalanceConfiguration
-org.apache.ignite.internal.management.cache.CacheScanTask
-org.apache.ignite.internal.management.cache.CacheScanTask$CacheScanJob
-org.apache.ignite.internal.management.cache.CacheScanTaskResult
+org.apache.ignite.internal.management.cache.scan.CacheScanTask
+org.apache.ignite.internal.management.cache.scan.CacheScanTask$CacheScanJob
+org.apache.ignite.internal.management.cache.scan.CacheScanTaskResult
org.apache.ignite.internal.management.cache.CacheStopTask
org.apache.ignite.internal.management.cache.CacheStopTask$CacheStopJob
org.apache.ignite.internal.management.cache.CacheStoreConfiguration
diff --git
a/modules/core/src/test/java/org/apache/ignite/platform/model/Employee.java
b/modules/core/src/test/java/org/apache/ignite/platform/model/Employee.java
index 6c63c29d684..687a0bcdc1e 100644
--- a/modules/core/src/test/java/org/apache/ignite/platform/model/Employee.java
+++ b/modules/core/src/test/java/org/apache/ignite/platform/model/Employee.java
@@ -25,6 +25,16 @@ public class Employee {
/** */
private long salary;
+ /** */
+ public Employee() {
+ }
+
+ /** */
+ public Employee(String fio, long salary) {
+ this.fio = fio;
+ this.salary = salary;
+ }
+
/** */
public String getFio() {
return fio;
diff --git
a/modules/core/src/test/java/org/apache/ignite/testframework/GridTestUtils.java
b/modules/core/src/test/java/org/apache/ignite/testframework/GridTestUtils.java
index b0ce812fb37..3e5000c6469 100644
---
a/modules/core/src/test/java/org/apache/ignite/testframework/GridTestUtils.java
+++
b/modules/core/src/test/java/org/apache/ignite/testframework/GridTestUtils.java
@@ -68,6 +68,8 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BooleanSupplier;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.cache.CacheException;
@@ -411,6 +413,28 @@ public final class GridTestUtils {
}
}
+ /**
+ * Checks that string {@param str} matches {@param pattern}. Logs both
strings
+ * and throws {@link java.lang.AssertionError}, if not.
+ *
+ * @param log Logger (optional).
+ * @param str String.
+ * @param pattern Pattern.
+ */
+ public static void assertContains(@Nullable IgniteLogger log, String str,
Pattern pattern) {
+ try {
+ Matcher m = pattern.matcher(str);
+ assertTrue(str != null && m.find());
+ }
+ catch (AssertionError e) {
+ U.warn(log, String.format("String does not match pattern: '%s':",
pattern));
+ U.warn(log, "String:");
+ U.warn(log, str);
+
+ throw e;
+ }
+ }
+
/**
* Checks that collection {@param col} contains element {@param elem}.
Logs collection, element
* and throws {@link java.lang.AssertionError}, if not.
diff --git
a/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassTest_cache_help.output
b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassTest_cache_help.output
index 6541838e748..db5136e1668 100644
---
a/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassTest_cache_help.output
+++
b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassTest_cache_help.output
@@ -141,9 +141,10 @@ Arguments: --cache help --yes
--group-names groupName1,groupName2,...groupNameN
- Comma-separated list of cache group names for which indexes should be
scheduled for the rebuild. Can be used simultaneously with cache names.
Show cache content:
- control.(sh|bat) --cache scan cacheName [--limit N]
+ control.(sh|bat) --cache scan cacheName [--output-format table] [--limit N]
Parameters:
+ --output-format table - Pluggable output format. 'default', 'table'
exists by default.
--limit N - limit count of entries to scan (1000 by default).
Command [CACHE] finished with code: 0
diff --git
a/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_cache_help.output
b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_cache_help.output
index 6541838e748..db5136e1668 100644
---
a/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_cache_help.output
+++
b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_cache_help.output
@@ -141,9 +141,10 @@ Arguments: --cache help --yes
--group-names groupName1,groupName2,...groupNameN
- Comma-separated list of cache group names for which indexes should be
scheduled for the rebuild. Can be used simultaneously with cache names.
Show cache content:
- control.(sh|bat) --cache scan cacheName [--limit N]
+ control.(sh|bat) --cache scan cacheName [--output-format table] [--limit N]
Parameters:
+ --output-format table - Pluggable output format. 'default', 'table'
exists by default.
--limit N - limit count of entries to scan (1000 by default).
Command [CACHE] finished with code: 0