com.github.helenusdriver.driver.tools.Tool Maven / Gradle / Ivy
/*
* Copyright (C) 2015-2015 The Helenus Driver Project Authors.
*
* 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 com.github.helenusdriver.driver.tools;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.LoggerConfig;
import com.datastax.driver.core.Cluster;
import com.datastax.driver.core.ConsistencyLevel;
import com.github.helenusdriver.commons.cli.RunnableFirstOption;
import com.github.helenusdriver.commons.cli.RunnableOption;
import com.github.helenusdriver.commons.collections.DirectedGraph;
import com.github.helenusdriver.commons.collections.GraphUtils;
import com.github.helenusdriver.commons.collections.graph.ConcurrentHashDirectedGraph;
import com.github.helenusdriver.commons.lang3.IllegalCycleException;
import com.github.helenusdriver.commons.lang3.SerializationUtils;
import com.github.helenusdriver.commons.lang3.reflect.ReflectionUtils;
import com.github.helenusdriver.driver.Batch;
import com.github.helenusdriver.driver.CreateSchema;
import com.github.helenusdriver.driver.CreateSchemas;
import com.github.helenusdriver.driver.GenericStatement;
import com.github.helenusdriver.driver.ObjectSet;
import com.github.helenusdriver.driver.ObjectStatement;
import com.github.helenusdriver.driver.Sequence;
import com.github.helenusdriver.driver.StatementBuilder;
import com.github.helenusdriver.driver.impl.StatementManagerImpl;
import com.github.helenusdriver.driver.info.ClassInfo;
import com.github.helenusdriver.driver.info.FieldInfo;
import com.github.helenusdriver.persistence.InitialObjects;
import org.reflections.Reflections;
/**
* The Tool
class defines a command line tool that can be used
* along with the driver.
*
* @copyright 2015-2015 The Helenus Driver Project Authors
*
* @author The Helenus Driver Project Authors
* @version 1 - Jan 19, 2015 - paouelle - Creation
*
* @since 2.0
*/
public class Tool {
/**
* Holds the statement manager.
*
* @author paouelle
*/
private static StatementManagerImpl mgr;
/**
* Holds the verbose flag.
*
* @author paouelle
*/
private static boolean vflag = false;
/**
* Holds the schemas creation action.
*
* @author paouelle
*/
@SuppressWarnings("serial")
private final static RunnableOption schemas
= new RunnableOption(
"s",
"schemas",
false,
"to define schemas for the specified pojo classes and/or packages (separated with :)"
) {
{
setArgs(Option.UNLIMITED_VALUES);
setArgName("classes-packages");
setValueSeparator(':');
}
@SuppressWarnings("synthetic-access")
@Override
public void run(CommandLine line) throws Exception {
Tool.createSchemas(line);
}
};
/**
* Holds the objects creation action.
*
* @author paouelle
*/
@SuppressWarnings("serial")
private final static RunnableOption objects
= new RunnableOption(
"o",
"objects",
false,
"to insert objects using the specified creator classes and/or packages (separated with :)"
) {
{
setArgs(Option.UNLIMITED_VALUES);
setArgName("classes-packages");
setValueSeparator(':');
}
@SuppressWarnings("synthetic-access")
@Override
public void run(CommandLine line) throws Exception {
Tool.insertObjects(line);
}
};
/**
* Holds the blob deserialization action.
*
* @author paouelle
*/
@SuppressWarnings("serial")
private final static RunnableFirstOption deserialize
= new RunnableFirstOption("d", "deserialize", true, "to deserialize a blob") {
{
setArgName("blob");
}
@Override
public void run(CommandLine line) throws Exception {
String s = line.getOptionValue(getLongOpt());
if (s.startsWith("0x") || s.startsWith("0X")) {
s = s.substring(2);
}
final byte[] blob = Hex.decodeHex(s.toCharArray());
if ((blob.length >= 2)
&& (blob[0] == (byte)0xac)
&& (blob[1] == (byte)0xed)) { // serialized blob
final Object obj
= org.apache.commons.lang3.SerializationUtils.deserialize(blob);
System.out.println(">> " + obj.getClass());
System.out.println(">> " + obj);
} else if ((blob.length >= 4)
&& (blob[0] == (byte)0xca)
&& (blob[1] == (byte)0xfe)
&& (blob[2] == (byte)0xba)
&& (blob[3] == (byte)0xbe)) { // compiled class
// see http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html
try (
final ByteArrayInputStream bais = new ByteArrayInputStream(blob);
final DataInputStream dis = new DataInputStream(bais);
) {
dis.readLong(); // skip header and class version
final int cpcnt = (dis.readShort() &0xffff) - 1;
final int[] classes = new int[cpcnt];
final String[] strings = new String[cpcnt];
for (int i = 0; i < cpcnt; i++) {
final int t = dis.read();
if (t == 7) { // u2
classes[i] = dis.readShort() & 0xffff;
} else if (t == 1) { // utf8
strings[i] = dis.readUTF(); // u2 + x * u1
} else if ((t == 5) || (t == 6)) { // u8
dis.readLong();
} else if ((t == 8) || (t == 16)) { // u2
dis.readShort();
} else if (t == 15) { // u3
dis.read();
dis.readShort();
} else { // u4 - t == 9, 10, 11, 3, 4, 12, 18
dis.readInt();
}
}
dis.readShort(); // skip access flags
System.out.println(
">> compiled class "
+ strings[classes[(dis.readShort() & 0xffff) - 1] - 1].replace(
'/', '.'
)
);
}
} else { // assume compressed serialized blob
final Object obj
= SerializationUtils.decompressAndDeserialize(blob);
System.out.println(">> " + obj.getClass());
System.out.println(">> " + obj);
}
System.exit(0);
}
};
/**
* Holds the help option.
*
* @author paouelle
*/
@SuppressWarnings("serial")
private final static RunnableOption help
= new RunnableOption("?", "help", false, "to print this message") {
@SuppressWarnings("synthetic-access")
@Override
public void run(CommandLine line) {
// automatically generate the help statement
final HelpFormatter formatter = new HelpFormatter();
formatter.printHelp(120, Tool.class.getSimpleName(), "Cassandra Client Tool", Tool.options, null, true);
}
};
/**
* Holds the verbose option.
*
* @author paouelle
*/
@SuppressWarnings("serial")
private final static RunnableOption verbose
= new RunnableOption("v", "verbose", false, "to enable verbose output") {
@SuppressWarnings("synthetic-access")
@Override
public void run(CommandLine line) {
Tool.vflag = true;
}
};
/**
* Holds the trace option.
*
* @author paouelle
*/
@SuppressWarnings("serial")
private final static RunnableOption trace
= new RunnableOption("t", "trace", false, "to enable trace output") {
@SuppressWarnings("synthetic-access")
@Override
public void run(CommandLine line) {
Tool.setRootLogLevel(Level.TRACE);
}
};
/**
* Holds the verbose option.
*
* @author paouelle
*/
@SuppressWarnings("static-access")
private final static Option filters = OptionBuilder
.withLongOpt("filters")
.withDescription("to specify entity filter classes to register with the driver (separated with :)")
.withValueSeparator(':')
.hasArgs()
.withArgName("classes")
.create("f");
/**
* Holds the Cassandra server option.
*
* @author paouelle
*/
@SuppressWarnings("static-access")
private final static Option server = OptionBuilder
.withLongOpt("server")
.withDescription("to specify the server address for Cassandra (defaults to localhost)")
.hasArg()
.withArgName("host")
.create();
/**
* Holds the Cassandra port option.
*
* @author paouelle
*/
@SuppressWarnings("static-access")
private final static Option port = OptionBuilder
.withLongOpt("port")
.withDescription("to specify the port number for Cassandra (defaults to 9160)")
.hasArg()
.withArgName("number")
.create();
/**
* Holds the option indicating if only matches should be considered otherwise
* all of them are created.
*
* @author paouelle
*/
@SuppressWarnings("static-access")
private final static Option matches_only = OptionBuilder
.withLongOpt("matches-only")
.withDescription("to specify that only keyspace that matches the specified suffixes should be created")
.create();
/**
* Holds the option indicating if dependent creators should not be considered
* otherwise all of them are created.
*
* @author paouelle
*/
@SuppressWarnings("static-access")
private final static Option no_dependents = OptionBuilder
.withLongOpt("no-dependents")
.withDescription("to specify that dependent creators should not be considered when creating objects")
.create();
/**
* Holds the Cassandra default replication factor option.
*
* @author paouelle
*/
@SuppressWarnings("static-access")
private final static Option replicationFactor = OptionBuilder
.withLongOpt("replication-factor")
.withDescription("to specify the default replication factor to use with the simple placement strategy when creating keyspaces (defaults to 2)")
.hasArg()
.withArgName("value")
.create();
/**
* Holds the suffix options.
*
* @author paouelle
*/
@SuppressWarnings("static-access")
private final static Option suffixes = OptionBuilder
.withDescription("to specify value(s) for suffix types (e.g. -Scustomer=acme, -Sregion=emea)")
.hasArgs(2)
.withArgName("type=value")
.withValueSeparator()
.create("S");
/**
* Holds the command-line options definition.
*
* @author paouelle
*/
private final static Options options
= (new Options()
.addOption(Tool.schemas)
.addOption(Tool.objects)
.addOption(Tool.suffixes)
.addOption(Tool.server)
.addOption(Tool.port)
.addOption(Tool.filters)
.addOption(Tool.deserialize)
.addOption(Tool.matches_only)
.addOption(Tool.no_dependents)
.addOption(Tool.replicationFactor)
.addOption(Tool.verbose)
.addOption(Tool.trace)
.addOption(Tool.help)
);
/**
* Executes the specified CQL statement.
*
* @author paouelle
*
* @param the type of POJO for the statement
*
* @param s the CQL statement to execute
* @return the result set from the execution
*/
@SuppressWarnings("unused")
private static ObjectSet executeCQL(ObjectStatement s) {
s.setConsistencyLevel(ConsistencyLevel.ONE);
if (Tool.vflag) {
System.out.println(
Tool.class.getSimpleName()
+ ": CQL -> "
+ s.getQueryString()
);
}
return s.execute();
}
/**
* Executes the specified CQL statement.
*
* @author paouelle
*
* @param s the CQL statement to execute
*/
private static void executeCQL(GenericStatement, ?> s) {
s.setConsistencyLevel(ConsistencyLevel.ONE);
s.setSerialConsistencyLevel(ConsistencyLevel.SERIAL);
if (Tool.vflag) {
final String query = s.getQueryString();
if (query == null) {
System.out.println(Tool.class.getSimpleName() + ": CQL -> null");
} else if ((query.length() < 2048) || !((s instanceof Batch) || (s instanceof Sequence))) {
System.out.println(
Tool.class.getSimpleName()
+ ": CQL -> "
+ query
);
} else {
if (s instanceof Batch) {
System.out.println(
Tool.class.getSimpleName()
+ ": CQL -> "
+ query.substring(0, 2024)
+ " ... APPLY BATCH;"
);
} else {
System.out.println(
Tool.class.getSimpleName()
+ ": CQL -> "
+ query.substring(0, 2024)
+ " ... APPLY SEQUENCE;"
);
}
}
}
s.execute();
}
/**
* Creates all defined schemas based on the provided set of class names and
* options and add the statements to the specified sequence. For each class
* found; the corresponding array element will be nulled. All others are
* simply skipped.
*
* @author paouelle
*
* @param cnames the set of class names to create schemas for
* @param suffixes the map of provided suffix values
* @param matching whether or not to only create schemas for keyspaces that
* matches the specified set of suffixes
* @param s the sequence where to add the generated statements
* @throws LinkageError if the linkage fails for one of the specified entity
* class
* @throws ExceptionInInitializerError if the initialization provoked by one
* of the specified entity class fails
*/
private static void createSchemasFromClasses(
String[] cnames,
Map suffixes,
boolean matching,
Sequence s
) {
next_class:
for (int i = 0; i < cnames.length; i++) {
try {
final Class> clazz = Class.forName(cnames[i]);
cnames[i] = null; // clear since we found a class
final CreateSchema> cs = StatementBuilder.createSchema(clazz);
cs.ifNotExists();
// pass all required suffixes
for (final Map.Entry e: suffixes.entrySet()) {
// check if this suffix type is defined
final FieldInfo> suffix = cs.getClassInfo().getSuffixKeyByType(e.getKey());
if (suffix != null) {
// register the suffix value with the corresponding suffix name
cs.where(
StatementBuilder.eq(suffix.getSuffixKeyName(), e.getValue())
);
} else if (matching) {
// we have one more suffix then defined with this pojo
// and we were requested to only do does that match the provided
// suffixes so skip the class
continue next_class;
}
}
s.add(cs);
for (final ClassInfo> cinfo: cs.getClassInfos()) {
System.out.println(
Tool.class.getSimpleName()
+ ": creating schema for "
+ cinfo.getObjectClass().getName()
);
}
} catch (ClassNotFoundException e) { // ignore and continue
}
}
}
/**
* Creates all defined schemas based on the provided set of package names and
* options and add the statements to the specified sequence.
*
* @author paouelle
*
* @param pkgs the set of packages to create schemas for
* @param suffixes the map of provided suffix values
* @param matching whether or not to only create schemas for keyspaces that
* matches the specified set of suffixes
* @param s the sequence where to add the generated statements
* @throws LinkageError if the linkage fails for one of the specified entity
* class
* @throws ExceptionInInitializerError if the initialization provoked by one
* of the specified entity class fails
* @throws IllegalArgumentException if no pojos are found in any of the
* specified packages
*/
private static void createSchemasFromPackages(
String[] pkgs,
Map suffixes,
boolean matching,
Sequence s
) {
for (final String pkg: pkgs) {
if (pkg == null) {
continue;
}
final CreateSchemas cs
= (matching
? StatementBuilder.createMatchingSchemas(pkg)
: StatementBuilder.createSchemas(pkg));
cs.ifNotExists();
// pass all suffixes
for (final Map.Entry e: suffixes.entrySet()) {
// register the suffix value with the corresponding suffix type
cs.where(
StatementBuilder.eq(e.getKey(), e.getValue())
);
}
for (final ClassInfo> cinfo: cs.getClassInfos()) {
System.out.println(
Tool.class.getSimpleName()
+ ": creating schema for "
+ cinfo.getObjectClass().getName()
);
}
s.add(cs);
}
}
/**
* Creates all defined schemas based on the provided command line information.
*
* @author paouelle
*
* @param line the command line information
* @throws Exception if an error occurs while creating schemas
* @throws LinkageError if the linkage fails for one of the specified entity
* class
* @throws ExceptionInInitializerError if the initialization provoked by one
* of the specified entity class fails
* @throws IllegalArgumentException if no pojos are found in any of the
* specified packages
*/
private static void createSchemas(CommandLine line) throws Exception {
final String[] opts = line.getOptionValues(Tool.schemas.getLongOpt());
@SuppressWarnings({"cast", "unchecked", "rawtypes"})
final Map suffixes
= (Map)(Map)line.getOptionProperties(Tool.suffixes.getOpt());
final boolean matching = line.hasOption(Tool.matches_only.getLongOpt());
final Sequence s = StatementBuilder.sequence();
System.out.print(
Tool.class.getSimpleName()
+ ": searching for schema definitions in "
+ Arrays.toString(opts)
);
if (!suffixes.isEmpty()) {
System.out.print(
" with "
+ (matching ? "matching " : "")
+ "suffixes "
+ suffixes
);
}
System.out.println();
// start by assuming we have classes; if we do they will be nulled from the array
Tool.createSchemasFromClasses(opts, suffixes, matching, s);
// now deal with the rest as if they were packages
Tool.createSchemasFromPackages(opts, suffixes, matching, s);
if (s.isEmpty() || (s.getQueryString() == null)) {
System.out.println(
Tool.class.getSimpleName()
+ ": no schemas found matching the specified criteria"
);
} else {
executeCQL(s);
}
}
/**
* Gets the initial objects to insert using the specified initial method and
* suffixes
*
* @author paouelle
*
* @param initial a non-null
initial method to retreive objects with
* @param suffixes the non-null
map of suffixes configured
* @return the initial objects to insert in the table or null
* if none needs to be inserted
*/
private static List
© 2015 - 2025 Weber Informatics LLC | Privacy Policy