Java tutorial
/* * 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.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; 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.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; import com.fasterxml.jackson.module.jsonSchema.JsonSchema; import com.fasterxml.jackson.module.jsonSchema.factories.SchemaFactoryWrapper; 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 <code>Tool</code> 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 schemas creation action. * * @author paouelle */ @SuppressWarnings("serial") private final static RunnableOption jsons = new RunnableOption("j", "jsons", false, "to write json schemas to disk 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.createJsonSchemas(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 Cassandra server option. * * @author paouelle */ @SuppressWarnings("static-access") private final static Option output = OptionBuilder.withLongOpt("output") .withDescription("to specify the output directory (defaults to current directory)").hasArg() .withArgName("output").create(); /** * Holds the command-line options definition. * * @author paouelle */ private final static Options options = (new Options().addOption(Tool.schemas).addOption(Tool.objects) .addOption(Tool.jsons).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.output) .addOption(Tool.verbose).addOption(Tool.trace).addOption(Tool.help)); /** * Executes the specified CQL statement. * * @author paouelle * * @param <T> 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 <T> ObjectSet<T> executeCQL(ObjectStatement<T> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> suffixes = (Map<String, String>) (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); } } /** * Creates all defined Json schemas based on the provided set of class names * and options and add the schemas to the specified map. 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 Json 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 schemas the map where to record the Json schema for the pojo classes * found * @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 IOException if an I/O error occurs while generating the Json schemas */ private static void createJsonSchemasFromClasses(String[] cnames, Map<String, String> suffixes, boolean matching, Map<Class<?>, JsonSchema> schemas) throws IOException { 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); // pass all required suffixes for (final Map.Entry<String, String> 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; } } for (final Class<?> c : cs.getObjectClasses()) { System.out.println(Tool.class.getSimpleName() + ": creating Json schema for " + c.getName()); final ObjectMapper m = new ObjectMapper(); final SchemaFactoryWrapper visitor = new SchemaFactoryWrapper(); m.registerModule(new Jdk8Module()); m.enable(SerializationFeature.INDENT_OUTPUT); m.acceptJsonFormatVisitor(m.constructType(c), visitor); schemas.put(c, visitor.finalSchema()); } } catch (ClassNotFoundException e) { // ignore and continue } } } /** * Creates all defined Json schemas based on the provided set of package names * and options and add the schemas to the specified map. * * @author paouelle * * @param pkgs the set of packages to create Json 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 schemas the map where to record the Json schema for the pojo classes * found * @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 * @throws IOException if an I/O error occurs while generating the Json schemas */ private static void createJsonSchemasFromPackages(String[] pkgs, Map<String, String> suffixes, boolean matching, Map<Class<?>, JsonSchema> schemas) throws IOException { for (final String pkg : pkgs) { if (pkg == null) { continue; } final CreateSchemas cs = (matching ? StatementBuilder.createMatchingSchemas(pkg) : StatementBuilder.createSchemas(pkg)); // pass all suffixes for (final Map.Entry<String, String> e : suffixes.entrySet()) { // register the suffix value with the corresponding suffix type cs.where(StatementBuilder.eq(e.getKey(), e.getValue())); } for (final Class<?> c : cs.getObjectClasses()) { System.out.println(Tool.class.getSimpleName() + ": creating Json schema for " + c.getName()); final ObjectMapper m = new ObjectMapper(); final SchemaFactoryWrapper visitor = new SchemaFactoryWrapper(); m.registerModule(new Jdk8Module()); m.enable(SerializationFeature.INDENT_OUTPUT); m.acceptJsonFormatVisitor(m.constructType(c), visitor); schemas.put(c, visitor.finalSchema()); } } } /** * Creates all defined Json 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 createJsonSchemas(CommandLine line) throws Exception { final String[] opts = line.getOptionValues(Tool.jsons.getLongOpt()); @SuppressWarnings({ "cast", "unchecked", "rawtypes" }) final Map<String, String> suffixes = (Map<String, String>) (Map) line .getOptionProperties(Tool.suffixes.getOpt()); final boolean matching = line.hasOption(Tool.matches_only.getLongOpt()); final Map<Class<?>, JsonSchema> schemas = new LinkedHashMap<>(); System.out.print( Tool.class.getSimpleName() + ": searching for Json 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.createJsonSchemasFromClasses(opts, suffixes, matching, schemas); // now deal with the rest as if they were packages Tool.createJsonSchemasFromPackages(opts, suffixes, matching, schemas); if (schemas.isEmpty()) { System.out.println( Tool.class.getSimpleName() + ": no Json schemas found matching the specified criteria"); } else { final String output = line.getOptionValue(Tool.output.getLongOpt(), "." // defaults to current directory ); final File dir = new File(output); if (!dir.exists()) { dir.mkdirs(); } org.apache.commons.lang3.Validate.isTrue(dir.isDirectory(), "not a directory: %s", dir); final ObjectMapper m = new ObjectMapper(); m.enable(SerializationFeature.INDENT_OUTPUT); for (final Map.Entry<Class<?>, JsonSchema> e : schemas.entrySet()) { m.writeValue(new File(dir, e.getKey().getName() + ".json"), e.getValue()); //System.out.println(s.getType() + " = " + m.writeValueAsString(s)); } } } /** * Gets the initial objects to insert using the specified initial method and * suffixes * * @author paouelle * * @param initial a non-<code>null</code> initial method to retreive objects with * @param suffixes the non-<code>null</code> map of suffixes configured * @return the initial objects to insert in the table or <code>null</code> * if none needs to be inserted */ private static List<Object> getInitialObjects(Method initial, Map<String, String> suffixes) { try { final Object array = initial.invoke(null, suffixes); if (array == null) { return Collections.emptyList(); } final int length = Array.getLength(array); final List<Object> objects = new ArrayList<>(length); for (int i = 0; i < length; i++) { objects.add(Array.get(array, i)); } return objects; } catch (IllegalAccessException e) { // should not happen throw new IllegalStateException(e); } catch (InvocationTargetException e) { final Throwable t = e.getTargetException(); if (t instanceof Error) { throw (Error) t; } else if (t instanceof RuntimeException) { throw (RuntimeException) t; } else { // we don't expect any of those throw new IllegalStateException(t); } } } /** * Finds an initial objects factory method and its dependent classes from the * specified object creator class. * * @author paouelle * * @param clazz the non-<code>null</code> object creator class * @return the initial objects factory method and its set of dependenc classes * or <code>null</code> if none configured * @throws IllegalArgumentException if the initial objects method is not * properly defined */ private static Pair<Method, Class<?>[]> findInitial(Class<?> clazz) { final InitialObjects io = clazz.getAnnotation(InitialObjects.class); if (io != null) { final String mname = io.staticMethod(); try { Method m; try { // first look for one with a map for suffixes m = clazz.getMethod(mname, Map.class); // validate that if suffixes are defined, the method expects a Map<String, String> // to provide the values for the suffixes when initializing objects final Class<?>[] cparms = m.getParameterTypes(); // should always be 1 as we used only 1 class in getMethod() if (cparms.length != 1) { throw new IllegalArgumentException( "expecting one Map<String, String> parameter for initial objects method '" + mname + "' in class: " + clazz.getSimpleName()); } // should always be a map as we used a Map to find the method if (!Map.class.isAssignableFrom(cparms[0])) { throw new IllegalArgumentException("expecting parameter for initial objects method '" + mname + "' to be of type Map<String, String> in class: " + clazz.getSimpleName()); } final Type[] tparms = m.getGenericParameterTypes(); // should always be 1 as we used only 1 class in getMethod() if (tparms.length != 1) { // should always be 1 as it was already tested above throw new IllegalArgumentException( "expecting one Map<String, String> parameter for initial objects method '" + mname + "' in class: " + clazz.getSimpleName()); } if (tparms[0] instanceof ParameterizedType) { final ParameterizedType ptype = (ParameterizedType) tparms[0]; // maps will always have 2 arguments for (final Type atype : ptype.getActualTypeArguments()) { final Class<?> aclazz = ReflectionUtils.getRawClass(atype); if (String.class != aclazz) { throw new IllegalArgumentException( "expecting a Map<String, String> parameter for initial objects method '" + mname + "' in class: " + clazz.getSimpleName()); } } } else { throw new IllegalArgumentException( "expecting a Map<String, String> parameter for initial objects method '" + mname + "' in class: " + clazz.getSimpleName()); } } catch (NoSuchMethodException e) { // fallback to one with no map m = clazz.getMethod(mname); } // validate the method is static if (!Modifier.isStatic(m.getModifiers())) { throw new IllegalArgumentException("initial objects method '" + mname + "' is not static in class: " + clazz.getSimpleName()); } // validate the return type is an array final Class<?> type = m.getReturnType(); if (!type.isArray()) { throw new IllegalArgumentException("initial objects method '" + mname + "' doesn't return an array in class: " + clazz.getSimpleName()); } return Pair.of(m, io.dependsOn()); } catch (NoSuchMethodException e) { throw new IllegalArgumentException( "missing initial objects method '" + mname + "' in class: " + clazz.getSimpleName(), e); } } return null; } /** * Finds object creators with their dependencies based on the provided set of * class names. For each class found; the corresponding array element will be * nulled. All others are simply skipped. * * @author paouelle * * @param classes the graph where to record creator classes * @param cnames the set of class names for object creators * @param no_dependents if dependents creators should not be considered * @throws LinkageError if the linkage fails for one entity class * @throws ExceptionInInitializerError if the initialization provoked by one * of one the entity class fails */ private static void findCreatorsFromClasses(DirectedGraph<Class<?>> classes, String[] cnames, boolean no_dependents) { 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 Pair<Method, Class<?>[]> initial = Tool.findInitial(clazz); if (initial == null) { System.out.println(Tool.class.getSimpleName() + ": no objects found using " + clazz.getName()); continue; } classes.add(clazz); final DirectedGraph.Node<Class<?>> node = classes.get(clazz); if (!no_dependents) { for (final Class<?> c : initial.getRight()) { node.add(c); } } } catch (ClassNotFoundException e) { // ignore and continue } } } /** * Finds object creators with their dependencies based on the provided set of * package names. * * @author paouelle * * @param classes the graph where to record creator classes * @param pkgs the set of packages to for creator objects * @param no_dependents if dependents creators should not be considered * @throws LinkageError if the linkage fails for one entity class * @throws ExceptionInInitializerError if the initialization provoked by one * of one the entity class fails */ private static void findCreatorsFromPackages(DirectedGraph<Class<?>> classes, String[] pkgs, boolean no_dependents) { for (final String pkg : pkgs) { if (pkg == null) { continue; } // search for all object creator classes for (final Class<?> clazz : new Reflections(pkg) .getTypesAnnotatedWith(com.github.helenusdriver.persistence.InitialObjects.class, true)) { final Pair<Method, Class<?>[]> initial = Tool.findInitial(clazz); if (initial == null) { System.out.println(Tool.class.getSimpleName() + ": no objects found using " + clazz.getName()); continue; } classes.add(clazz); final DirectedGraph.Node<Class<?>> node = classes.get(clazz); if (!no_dependents) { for (final Class<?> c : initial.getRight()) { node.add(c); } } } } } /** * Inserts objects from all object creators based on the provided collection * of classes and options. * * @author paouelle * * @param classes the collection of classes for object creators * @param suffixes the map of provided suffix values */ private static void insertObjectsFromClasses(Collection<Class<?>> classes, Map<String, String> suffixes) { for (final Class<?> clazz : classes) { final Pair<Method, Class<?>[]> initial = Tool.findInitial(clazz); if (initial == null) { // should not happen! System.out.println(Tool.class.getSimpleName() + ": no objects found using " + clazz.getName()); continue; } final List<Object> ios = Tool.getInitialObjects(initial.getLeft(), suffixes); System.out.println(Tool.class.getSimpleName() + ": inserting " + ios.size() + " object" + (ios.size() == 1 ? "" : "s") + " using " + clazz.getName()); final Batch b = StatementBuilder.batch(); for (final Object io : ios) { b.add(StatementBuilder.insert(io)); } if (b.isEmpty() || (b.getQueryString() == null)) { System.out.println(Tool.class.getSimpleName() + ": no objects to insert"); } else { executeCQL(b); } } } /** * Inserts all defined objects based on the provided command line information. * * @author paouelle * * @param line the command line information * @throws Exception if an error occurs while inserting objects * @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 ClassNotFoundException if one of the object creator class is not * found * @throws IllegalCycleException if a dependency cycle is detected in the * classes found */ private static void insertObjects(CommandLine line) throws Exception { final String[] opts = line.getOptionValues(Tool.objects.getLongOpt()); @SuppressWarnings({ "cast", "unchecked", "rawtypes" }) final Map<String, String> suffixes = (Map<String, String>) (Map) line .getOptionProperties(Tool.suffixes.getOpt()); final boolean no_dependents = line.hasOption(Tool.no_dependents.getLongOpt()); System.out .print(Tool.class.getSimpleName() + ": searching for object creators in " + Arrays.toString(opts)); if (!suffixes.isEmpty()) { System.out.print(" with suffixes " + suffixes); } if (no_dependents) { System.out.print(" not including dependent creators"); } System.out.println(); final DirectedGraph<Class<?>> classes = new ConcurrentHashDirectedGraph<>(); // start by assuming we have classes; if we do they will be nulled from the array Tool.findCreatorsFromClasses(classes, opts, no_dependents); // now deal with the rest as if they were packages Tool.findCreatorsFromPackages(classes, opts, no_dependents); // now do a reverse topological sort of the specified graph of classes such that // we end up creating objects in the dependent order try { final List<Class<?>> cs = GraphUtils.sort(classes); Collections.reverse(cs); Tool.insertObjectsFromClasses(cs, suffixes); } catch (IllegalCycleException e) { System.out.println( Tool.class.getSimpleName() + ": circular creator dependency detected: " + e.getCycle()); throw e; } } /** * Sets the root and helenus loggers log level. * * @author paouelle * * @param level the level to which the root logger should be at */ private static void setRootLogLevel(Level level) { final LoggerContext ctx = (LoggerContext) LogManager.getContext(false); final Configuration config = ctx.getConfiguration(); final String pkg = Tool.class.getPackage().getName(); for (final String name : new String[] { LogManager.ROOT_LOGGER_NAME, pkg.subSequence(0, pkg.lastIndexOf('.', pkg.lastIndexOf('.') - 1)).toString() }) { final LoggerConfig loggerConfig = config.getLoggerConfig(name); loggerConfig.setLevel(level); } // This causes all Loggers to re-fetch information from their LoggerConfig. ctx.updateLoggers(); } /** * Main point of entry for this tool * * @author paouelle * * @param args the command-line arguments to the tool */ public static void main(String[] args) { Tool.setRootLogLevel(Level.OFF); // disable logging by default try { final CommandLineParser parser = new GnuParser(); final CommandLine line = parser.parse(Tool.options, args); if (line.hasOption(Tool.verbose.getOpt())) { // do this one first Tool.verbose.run(line); } if (line.hasOption(Tool.trace.getOpt())) { // do this one next Tool.trace.run(line); } for (final Option option : line.getOptions()) { // run these first if (option instanceof RunnableFirstOption) { ((RunnableFirstOption) option).run(line); } } final String server = line.getOptionValue(Tool.server.getLongOpt(), "127.0.0.1" // defaults to local host ); final boolean connect = (line.hasOption(Tool.objects.getLongOpt()) || line.hasOption(Tool.schemas.getLongOpt())); Tool.mgr = new StatementManagerImpl(Cluster.builder().addContactPoint(server).withQueryOptions(null), connect, line.getOptionValues(Tool.filters.getLongOpt())); if (line.hasOption(Tool.replicationFactor.getLongOpt())) { mgr.setDefaultReplicationFactor( Integer.parseInt(line.getOptionValue(Tool.replicationFactor.getLongOpt()))); } try { if (connect && Tool.vflag) { System.out.println(Tool.class.getSimpleName() + ": connected to Cassandra on: " + server); } for (final Option option : line.getOptions()) { if (option instanceof RunnableOption) { ((RunnableOption) option).run(line); } } } finally { Tool.mgr.close().get(); // shutdown and wait for its completion } } catch (Exception e) { System.err.print(Tool.class.getSimpleName() + ": unexpected exception: "); e.printStackTrace(System.err); System.exit(1); } } }