blob: a3bc5a252d4ed0837eb074fba7e9035c2d8a0d41 [file] [log] [blame]
/*
* 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.accumulo.shell.commands;
import java.io.IOException;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.accumulo.core.client.AccumuloException;
import org.apache.accumulo.core.client.AccumuloSecurityException;
import org.apache.accumulo.core.client.IteratorSetting;
import org.apache.accumulo.core.client.NamespaceNotFoundException;
import org.apache.accumulo.core.client.TableNotFoundException;
import org.apache.accumulo.core.data.Key;
import org.apache.accumulo.core.data.Value;
import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
import org.apache.accumulo.core.iterators.OptionDescriber;
import org.apache.accumulo.core.iterators.OptionDescriber.IteratorOptions;
import org.apache.accumulo.core.iterators.SortedKeyValueIterator;
import org.apache.accumulo.core.iterators.user.AgeOffFilter;
import org.apache.accumulo.core.iterators.user.RegExFilter;
import org.apache.accumulo.core.iterators.user.ReqVisFilter;
import org.apache.accumulo.core.iterators.user.VersioningIterator;
import org.apache.accumulo.shell.Shell;
import org.apache.accumulo.shell.Shell.Command;
import org.apache.accumulo.shell.ShellCommandException;
import org.apache.accumulo.shell.ShellCommandException.ErrorCode;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionGroup;
import org.apache.commons.cli.Options;
import org.apache.commons.lang.StringUtils;
import jline.console.ConsoleReader;
public class SetIterCommand extends Command {
private Option allScopeOpt, mincScopeOpt, majcScopeOpt, scanScopeOpt;
Option profileOpt, priorityOpt, nameOpt;
Option aggTypeOpt, ageoffTypeOpt, regexTypeOpt, versionTypeOpt, reqvisTypeOpt, classnameTypeOpt;
@Override
public int execute(final String fullCommand, final CommandLine cl, final Shell shellState)
throws AccumuloException, AccumuloSecurityException, TableNotFoundException, IOException,
ShellCommandException {
boolean tables = cl.hasOption(OptUtil.tableOpt().getOpt())
|| !shellState.getTableName().isEmpty();
boolean namespaces = cl.hasOption(OptUtil.namespaceOpt().getOpt());
final int priority = Integer.parseInt(cl.getOptionValue(priorityOpt.getOpt()));
final Map<String,String> options = new HashMap<>();
String classname = cl.getOptionValue(classnameTypeOpt.getOpt());
if (cl.hasOption(aggTypeOpt.getOpt())) {
Shell.log.warn("aggregators are deprecated");
@SuppressWarnings("deprecation")
String deprecatedClassName = org.apache.accumulo.core.iterators.AggregatingIterator.class
.getName();
classname = deprecatedClassName;
} else if (cl.hasOption(regexTypeOpt.getOpt())) {
classname = RegExFilter.class.getName();
} else if (cl.hasOption(ageoffTypeOpt.getOpt())) {
classname = AgeOffFilter.class.getName();
} else if (cl.hasOption(versionTypeOpt.getOpt())) {
classname = VersioningIterator.class.getName();
} else if (cl.hasOption(reqvisTypeOpt.getOpt())) {
classname = ReqVisFilter.class.getName();
}
// ACCUMULO-4791: The SetIterCommand class as well as methods within the Shell.java class all
// require that a table or namespace be provided or otherwise they will not execute. But the
// setShellIter command does not require either of these values. In order to get around
// this requirement we will check to see if a profile name has been provided (indicating that
// we are setting a shell iterator). If so, temporarily set the table state to an
// existing table such as accumulo.metadata. This allows the command to complete successfully.
// After completion reassign the table to its original value and continue.
String currentTableName = null;
String tmpTable = null;
String configuredName;
try {
if (profileOpt != null && StringUtils.isBlank(shellState.getTableName())) {
currentTableName = shellState.getTableName();
tmpTable = "accumulo.metadata";
shellState.setTableName(tmpTable);
tables = cl.hasOption(OptUtil.tableOpt().getOpt()) || !shellState.getTableName().isEmpty();
}
ClassLoader classloader = shellState.getClassLoader(cl, shellState);
// Get the iterator options, with potentially a name provided by the OptionDescriber impl or
// through user input
configuredName = setUpOptions(classloader, shellState.getReader(), classname, options);
} finally {
// ACCUMULO-4792: reset table name and continue
if (tmpTable != null) {
shellState.setTableName(currentTableName);
}
}
// Try to get the name provided by the setiter command
String name = cl.getOptionValue(nameOpt.getOpt(), null);
// Cannot continue if no name is provided
if (null == name && null == configuredName) {
throw new IllegalArgumentException("No provided or default name for iterator");
} else if (null == name) {
// Fall back to the name from OptionDescriber or user input if none is provided on setiter
// option
name = configuredName;
}
if (namespaces) {
try {
setNamespaceProperties(cl, shellState, priority, options, classname, name);
} catch (NamespaceNotFoundException e) {
throw new IllegalArgumentException(e);
}
} else if (tables) {
setTableProperties(cl, shellState, priority, options, classname, name);
} else {
throw new IllegalArgumentException("No table or namespace specified");
}
return 0;
}
protected void setTableProperties(final CommandLine cl, final Shell shellState,
final int priority, final Map<String,String> options, final String classname,
final String name) throws AccumuloException, AccumuloSecurityException, ShellCommandException,
TableNotFoundException {
// remove empty values
final String tableName = OptUtil.getTableOpt(cl, shellState);
ScanCommand.ensureTserversCanLoadIterator(shellState, tableName, classname);
final String aggregatorClass = options.get("aggregatorClass");
// @formatter:off
@SuppressWarnings("deprecation")
String deprecatedAggregatorClassName =
org.apache.accumulo.core.iterators.aggregation.Aggregator.class.getName();
// @formatter:on
if (aggregatorClass != null && !shellState.getConnector().tableOperations()
.testClassLoad(tableName, aggregatorClass, deprecatedAggregatorClassName)) {
throw new ShellCommandException(ErrorCode.INITIALIZATION_FAILURE,
"Servers are unable to load " + aggregatorClass + " as type "
+ deprecatedAggregatorClassName);
}
for (Iterator<Entry<String,String>> i = options.entrySet().iterator(); i.hasNext();) {
final Entry<String,String> entry = i.next();
if (entry.getValue() == null || entry.getValue().isEmpty()) {
i.remove();
}
}
final EnumSet<IteratorScope> scopes = EnumSet.noneOf(IteratorScope.class);
if (cl.hasOption(allScopeOpt.getOpt()) || cl.hasOption(mincScopeOpt.getOpt())) {
scopes.add(IteratorScope.minc);
}
if (cl.hasOption(allScopeOpt.getOpt()) || cl.hasOption(majcScopeOpt.getOpt())) {
scopes.add(IteratorScope.majc);
}
if (cl.hasOption(allScopeOpt.getOpt()) || cl.hasOption(scanScopeOpt.getOpt())) {
scopes.add(IteratorScope.scan);
}
if (scopes.isEmpty()) {
throw new IllegalArgumentException("You must select at least one scope to configure");
}
final IteratorSetting setting = new IteratorSetting(priority, name, classname, options);
shellState.getConnector().tableOperations().attachIterator(tableName, setting, scopes);
}
protected void setNamespaceProperties(final CommandLine cl, final Shell shellState,
final int priority, final Map<String,String> options, final String classname,
final String name) throws AccumuloException, AccumuloSecurityException, ShellCommandException,
NamespaceNotFoundException {
// remove empty values
final String namespace = OptUtil.getNamespaceOpt(cl, shellState);
if (!shellState.getConnector().namespaceOperations().testClassLoad(namespace, classname,
SortedKeyValueIterator.class.getName())) {
throw new ShellCommandException(ErrorCode.INITIALIZATION_FAILURE,
"Servers are unable to load " + classname + " as type "
+ SortedKeyValueIterator.class.getName());
}
final String aggregatorClass = options.get("aggregatorClass");
// @formatter:off
@SuppressWarnings("deprecation")
String deprecatedAggregatorClassName =
org.apache.accumulo.core.iterators.aggregation.Aggregator.class.getName();
// @formatter:on
if (aggregatorClass != null && !shellState.getConnector().namespaceOperations()
.testClassLoad(namespace, aggregatorClass, deprecatedAggregatorClassName)) {
throw new ShellCommandException(ErrorCode.INITIALIZATION_FAILURE,
"Servers are unable to load " + aggregatorClass + " as type "
+ deprecatedAggregatorClassName);
}
for (Iterator<Entry<String,String>> i = options.entrySet().iterator(); i.hasNext();) {
final Entry<String,String> entry = i.next();
if (entry.getValue() == null || entry.getValue().isEmpty()) {
i.remove();
}
}
final EnumSet<IteratorScope> scopes = EnumSet.noneOf(IteratorScope.class);
if (cl.hasOption(allScopeOpt.getOpt()) || cl.hasOption(mincScopeOpt.getOpt())) {
scopes.add(IteratorScope.minc);
}
if (cl.hasOption(allScopeOpt.getOpt()) || cl.hasOption(majcScopeOpt.getOpt())) {
scopes.add(IteratorScope.majc);
}
if (cl.hasOption(allScopeOpt.getOpt()) || cl.hasOption(scanScopeOpt.getOpt())) {
scopes.add(IteratorScope.scan);
}
if (scopes.isEmpty()) {
throw new IllegalArgumentException("You must select at least one scope to configure");
}
final IteratorSetting setting = new IteratorSetting(priority, name, classname, options);
shellState.getConnector().namespaceOperations().attachIterator(namespace, setting, scopes);
}
private static String setUpOptions(ClassLoader classloader, final ConsoleReader reader,
final String className, final Map<String,String> options)
throws IOException, ShellCommandException {
String input;
@SuppressWarnings("rawtypes")
SortedKeyValueIterator untypedInstance;
@SuppressWarnings("rawtypes")
Class<? extends SortedKeyValueIterator> clazz;
try {
clazz = classloader.loadClass(className).asSubclass(SortedKeyValueIterator.class);
untypedInstance = clazz.newInstance();
} catch (ClassNotFoundException e) {
StringBuilder msg = new StringBuilder("Unable to load ").append(className);
if (className.indexOf('.') < 0) {
msg.append("; did you use a fully qualified package name?");
} else {
msg.append("; class not found.");
}
throw new ShellCommandException(ErrorCode.INITIALIZATION_FAILURE, msg.toString());
} catch (InstantiationException e) {
throw new IllegalArgumentException(e.getMessage());
} catch (IllegalAccessException e) {
throw new IllegalArgumentException(e.getMessage());
} catch (ClassCastException e) {
String msg = className + " loaded successfully but does not implement SortedKeyValueIterator."
+ " This class cannot be used with this command.";
throw new ShellCommandException(ErrorCode.INITIALIZATION_FAILURE, msg);
}
@SuppressWarnings("unchecked")
SortedKeyValueIterator<Key,Value> skvi = untypedInstance;
OptionDescriber iterOptions = null;
if (OptionDescriber.class.isAssignableFrom(skvi.getClass())) {
iterOptions = (OptionDescriber) skvi;
}
String iteratorName;
if (null != iterOptions) {
final IteratorOptions itopts = iterOptions.describeOptions();
iteratorName = itopts.getName();
if (iteratorName == null) {
throw new IllegalArgumentException(
className + " described its default distinguishing name as null");
}
String shortClassName = className;
if (className.contains(".")) {
shortClassName = className.substring(className.lastIndexOf('.') + 1);
}
final Map<String,String> localOptions = new HashMap<>();
do {
// clean up the overall options that caused things to fail
for (String key : localOptions.keySet()) {
options.remove(key);
}
localOptions.clear();
reader.println(itopts.getDescription());
String prompt;
if (itopts.getNamedOptions() != null) {
for (Entry<String,String> e : itopts.getNamedOptions().entrySet()) {
prompt = Shell.repeat("-", 10) + "> set " + shortClassName + " parameter " + e.getKey()
+ ", " + e.getValue() + ": ";
reader.flush();
input = reader.readLine(prompt);
if (input == null) {
reader.println();
throw new IOException("Input stream closed");
}
// Places all Parameters and Values into the LocalOptions, even if the value is "".
// This allows us to check for "" values when setting the iterators and allows us to
// remove
// the parameter and value from the table property.
localOptions.put(e.getKey(), input);
}
}
if (itopts.getUnnamedOptionDescriptions() != null) {
for (String desc : itopts.getUnnamedOptionDescriptions()) {
reader.println(Shell.repeat("-", 10) + "> entering options: " + desc);
input = "start";
prompt = Shell.repeat("-", 10) + "> set " + shortClassName
+ " option (<name> <value>, hit enter to skip): ";
while (true) {
reader.flush();
input = reader.readLine(prompt);
if (input == null) {
reader.println();
throw new IOException("Input stream closed");
} else {
input = new String(input);
}
if (input.length() == 0)
break;
String[] sa = input.split(" ", 2);
localOptions.put(sa[0], sa[1]);
}
}
}
options.putAll(localOptions);
if (!iterOptions.validateOptions(options))
reader.println("invalid options for " + clazz.getName());
} while (!iterOptions.validateOptions(options));
} else {
reader.flush();
reader.println("The iterator class does not implement OptionDescriber."
+ " Consider this for better iterator configuration using this setiter" + " command.");
iteratorName = reader.readLine("Name for iterator (enter to skip): ");
if (null == iteratorName) {
reader.println();
throw new IOException("Input stream closed");
} else if (StringUtils.isWhitespace(iteratorName)) {
// Treat whitespace or empty string as no name provided
iteratorName = null;
}
reader.flush();
reader.println("Optional, configure name-value options for iterator:");
String prompt = Shell.repeat("-", 10) + "> set option (<name> <value>, hit enter to skip): ";
final HashMap<String,String> localOptions = new HashMap<>();
while (true) {
reader.flush();
input = reader.readLine(prompt);
if (input == null) {
reader.println();
throw new IOException("Input stream closed");
} else if (StringUtils.isWhitespace(input)) {
break;
}
String[] sa = input.split(" ", 2);
localOptions.put(sa[0], sa[1]);
}
options.putAll(localOptions);
}
return iteratorName;
}
@Override
public String description() {
return "sets a table-specific or namespace-specific iterator";
}
// Set all options common to both iterators and shell iterators
protected void setBaseOptions(Options options) {
setPriorityOptions(options);
setNameOptions(options);
setIteratorTypeOptions(options);
}
private void setNameOptions(Options options) {
nameOpt = new Option("n", "name", true, "iterator to set");
nameOpt.setArgName("itername");
options.addOption(nameOpt);
}
private void setPriorityOptions(Options options) {
priorityOpt = new Option("p", "priority", true, "the order in which the iterator is applied");
priorityOpt.setArgName("pri");
priorityOpt.setRequired(true);
options.addOption(priorityOpt);
}
@Override
public Options getOptions() {
final Options o = new Options();
setBaseOptions(o);
setScopeOptions(o);
setTableOptions(o);
return o;
}
private void setScopeOptions(Options o) {
allScopeOpt = new Option("all", "all-scopes", false,
"applied at scan time, minor and major compactions");
mincScopeOpt = new Option(IteratorScope.minc.name(), "minor-compaction", false,
"applied at minor compaction");
majcScopeOpt = new Option(IteratorScope.majc.name(), "major-compaction", false,
"applied at major compaction");
scanScopeOpt = new Option(IteratorScope.scan.name(), "scan-time", false,
"applied at scan time");
o.addOption(allScopeOpt);
o.addOption(mincScopeOpt);
o.addOption(majcScopeOpt);
o.addOption(scanScopeOpt);
}
private void setTableOptions(Options o) {
final OptionGroup tableGroup = new OptionGroup();
tableGroup.addOption(OptUtil.tableOpt("table to configure iterators on"));
tableGroup.addOption(OptUtil.namespaceOpt("namespace to configure iterators on"));
o.addOptionGroup(tableGroup);
}
private void setIteratorTypeOptions(Options o) {
final OptionGroup typeGroup = new OptionGroup();
classnameTypeOpt = new Option("class", "class-name", true,
"a java class that implements SortedKeyValueIterator");
classnameTypeOpt.setArgName("name");
aggTypeOpt = new Option("agg", "aggregator", false, "an aggregating type");
regexTypeOpt = new Option("regex", "regular-expression", false, "a regex matching iterator");
versionTypeOpt = new Option("vers", "version", false, "a versioning iterator");
reqvisTypeOpt = new Option("reqvis", "require-visibility", false,
"an iterator that omits entries with empty visibilities");
ageoffTypeOpt = new Option("ageoff", "ageoff", false, "an aging off iterator");
typeGroup.addOption(classnameTypeOpt);
typeGroup.addOption(aggTypeOpt);
typeGroup.addOption(regexTypeOpt);
typeGroup.addOption(versionTypeOpt);
typeGroup.addOption(reqvisTypeOpt);
typeGroup.addOption(ageoffTypeOpt);
typeGroup.setRequired(true);
o.addOptionGroup(typeGroup);
}
@Override
public int numArgs() {
return 0;
}
}