Java tutorial
/* * * 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.metron.stellar.common.shell; import com.fasterxml.jackson.core.type.TypeReference; import com.google.common.collect.Iterables; import com.google.common.collect.Maps; import java.io.ByteArrayInputStream; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.Properties; import java.util.SortedMap; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.apache.commons.collections4.trie.PatriciaTrie; import org.apache.commons.lang.StringUtils; import org.apache.curator.framework.CuratorFramework; import org.apache.metron.stellar.common.StellarProcessor; import org.apache.metron.stellar.common.configuration.ConfigurationsUtils; import org.apache.metron.stellar.common.utils.JSONUtils; import org.apache.metron.stellar.dsl.Context; import org.apache.metron.stellar.dsl.MapVariableResolver; import org.apache.metron.stellar.dsl.StellarFunctionInfo; import org.apache.metron.stellar.dsl.StellarFunctions; import org.apache.metron.stellar.dsl.VariableResolver; import org.apache.metron.stellar.dsl.functions.resolver.FunctionResolver; import org.jboss.aesh.console.Console; import static org.apache.metron.stellar.common.configuration.ConfigurationsUtils.readGlobalConfigBytesFromZookeeper; import static org.apache.metron.stellar.common.shell.StellarExecutor.OperationType.DOC; import static org.apache.metron.stellar.common.shell.StellarExecutor.OperationType.NORMAL; import static org.apache.metron.stellar.dsl.Context.Capabilities.GLOBAL_CONFIG; import static org.apache.metron.stellar.dsl.Context.Capabilities.STELLAR_CONFIG; import static org.apache.metron.stellar.dsl.Context.Capabilities.ZOOKEEPER_CLIENT; /** * Executes Stellar expressions and maintains state across multiple invocations. */ public class StellarExecutor { public static String SHELL_VARIABLES = "shellVariables"; public static String CONSOLE = "console"; private ReadWriteLock indexLock = new ReentrantReadWriteLock(); public static class VariableResult { private String expression; private Object result; public VariableResult(String expression, Object result) { this.expression = expression; this.result = result; } public String getExpression() { return expression; } public Object getResult() { return result; } @Override public String toString() { String ret = "" + result; if (expression != null) { ret += " via " + expression; } return ret; } } /** * Prefix tree index of auto-completes. */ private PatriciaTrie<AutoCompleteType> autocompleteIndex; /** * The variables known by Stellar. */ private Map<String, VariableResult> variables; /** * The function resolver. */ private FunctionResolver functionResolver; /** * A Zookeeper client. Only defined if given a valid Zookeeper URL. */ private Optional<CuratorFramework> client; /** * The Stellar execution context. */ private Context context; private Console console; public enum OperationType { DOC, MAGIC, NORMAL } public interface AutoCompleteTransformation { String transform(OperationType type, String key); } public enum AutoCompleteType implements AutoCompleteTransformation { FUNCTION((type, key) -> { if (type == DOC) { return StellarShell.DOC_PREFIX + key; } else if (type == NORMAL) { return key + "("; } return key; }), VARIABLE((type, key) -> key), TOKEN((type, key) -> key); AutoCompleteTransformation transform; AutoCompleteType(AutoCompleteTransformation transform) { this.transform = transform; } @Override public String transform(OperationType type, String key) { return transform.transform(type, key); } } /** * @param console The console used to drive the REPL. * @param properties The Stellar properties. * @throws Exception */ public StellarExecutor(Console console, Properties properties) throws Exception { this(null, console, properties); } /** * @param console The console used to drive the REPL. * @param properties The Stellar properties. * @throws Exception */ public StellarExecutor(String zookeeperUrl, Console console, Properties properties) throws Exception { this.variables = new HashMap<>(); this.client = createClient(zookeeperUrl); this.context = createContext(properties); // initialize the default function resolver StellarFunctions.initialize(this.context); this.functionResolver = StellarFunctions.FUNCTION_RESOLVER(); this.autocompleteIndex = initializeIndex(); this.console = console; // asynchronously update the index with function names found from a classpath scan. new Thread(() -> { Iterable<StellarFunctionInfo> functions = functionResolver.getFunctionInfo(); indexLock.writeLock().lock(); try { for (StellarFunctionInfo info : functions) { String functionName = info.getName(); autocompleteIndex.put(functionName, AutoCompleteType.FUNCTION); } } finally { System.out.println("Functions loaded, you may refer to functions now..."); indexLock.writeLock().unlock(); } }).start(); } private PatriciaTrie<AutoCompleteType> initializeIndex() { Map<String, AutoCompleteType> index = new HashMap<>(); index.put("==", AutoCompleteType.TOKEN); index.put(">=", AutoCompleteType.TOKEN); index.put("<=", AutoCompleteType.TOKEN); index.put(":=", AutoCompleteType.TOKEN); index.put("quit", AutoCompleteType.TOKEN); index.put(StellarShell.MAGIC_FUNCTIONS, AutoCompleteType.FUNCTION); index.put(StellarShell.MAGIC_VARS, AutoCompleteType.FUNCTION); index.put(StellarShell.MAGIC_GLOBALS, AutoCompleteType.FUNCTION); index.put(StellarShell.MAGIC_DEFINE, AutoCompleteType.FUNCTION); index.put(StellarShell.MAGIC_UNDEFINE, AutoCompleteType.FUNCTION); return new PatriciaTrie<>(index); } public Iterable<String> autoComplete(String buffer, final OperationType opType) { indexLock.readLock().lock(); try { SortedMap<String, AutoCompleteType> ret = autocompleteIndex.prefixMap(buffer); if (ret.isEmpty()) { return new ArrayList<>(); } return Iterables.transform(ret.entrySet(), kv -> kv.getValue().transform(opType, kv.getKey())); } finally { indexLock.readLock().unlock(); } } /** * Creates a Zookeeper client. * @param zookeeperUrl The Zookeeper URL. */ private Optional<CuratorFramework> createClient(String zookeeperUrl) { // can only create client, if have valid zookeeper URL if (StringUtils.isNotBlank(zookeeperUrl)) { CuratorFramework client = ConfigurationsUtils.getClient(zookeeperUrl); client.start(); return Optional.of(client); } else { return Optional.empty(); } } /** * Creates a Context initialized with configuration stored in Zookeeper. */ private Context createContext(Properties properties) throws Exception { Context.Builder contextBuilder = new Context.Builder().with(SHELL_VARIABLES, () -> variables) .with(CONSOLE, () -> console).with(STELLAR_CONFIG, () -> properties); // load global configuration from zookeeper if (client.isPresent()) { // fetch the global configuration Map<String, Object> global = JSONUtils.INSTANCE.load( new ByteArrayInputStream(readGlobalConfigBytesFromZookeeper(client.get())), new TypeReference<Map<String, Object>>() { }); contextBuilder.with(GLOBAL_CONFIG, () -> global).with(ZOOKEEPER_CLIENT, () -> client.get()) .with(STELLAR_CONFIG, () -> getStellarConfig(global, properties)); } return contextBuilder.build(); } private Map<String, Object> getStellarConfig(Map<String, Object> globalConfig, Properties props) { Map<String, Object> ret = new HashMap<>(); ret.putAll(globalConfig); if (props != null) { for (Map.Entry<Object, Object> kv : props.entrySet()) { ret.put(kv.getKey().toString(), kv.getValue()); } } return ret; } /** * Executes the Stellar expression and returns the result. * @param expression The Stellar expression to execute. * @return The result of the expression. */ public Object execute(String expression) { VariableResolver variableResolver = new MapVariableResolver( Maps.transformValues(variables, result -> result.getResult()), Collections.emptyMap()); StellarProcessor processor = new StellarProcessor(); return processor.parse(expression, variableResolver, functionResolver, context); } /** * Assigns a value to a variable. * @param variable The name of the variable. * @param value The value of the variable */ public void assign(String variable, String expression, Object value) { this.variables.put(variable, new VariableResult(expression, value)); indexLock.writeLock().lock(); try { if (value != null) { this.autocompleteIndex.put(variable, AutoCompleteType.VARIABLE); } else { this.autocompleteIndex.remove(variable); } } finally { indexLock.writeLock().unlock(); } } public Map<String, VariableResult> getVariables() { return this.variables; } public FunctionResolver getFunctionResolver() { return functionResolver; } public Context getContext() { return context; } }