/* * 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.solr.client.solrj.io.stream.expr; import java.io.IOException; import java.io.Serializable; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.stream.Collectors; import org.apache.solr.client.solrj.io.comp.ComparatorOrder; import org.apache.solr.client.solrj.io.comp.MultipleFieldComparator; import org.apache.solr.client.solrj.io.comp.StreamComparator; import org.apache.solr.client.solrj.io.eq.MultipleFieldEqualitor; import org.apache.solr.client.solrj.io.eq.StreamEqualitor; import org.apache.solr.client.solrj.io.eval.StreamEvaluator; import org.apache.solr.client.solrj.io.ops.StreamOperation; import org.apache.solr.client.solrj.io.stream.TupleStream; import org.apache.solr.client.solrj.io.stream.metrics.Metric; /** * Used to convert strings into stream expressions */ public class StreamFactory implements Serializable { private transient HashMap<String,String> collectionZkHosts; private transient HashMap<String,Class<? extends Expressible>> functionNames; private transient String defaultZkHost; public StreamFactory(){ collectionZkHosts = new HashMap<>(); functionNames = new HashMap<>(); } public StreamFactory withCollectionZkHost(String collectionName, String zkHost){ this.collectionZkHosts.put(collectionName, zkHost); return this; } public StreamFactory withDefaultZkHost(String zkHost) { this.defaultZkHost = zkHost; return this; } public String getDefaultZkHost() { return this.defaultZkHost; } public String getCollectionZkHost(String collectionName){ if(this.collectionZkHosts.containsKey(collectionName)){ return this.collectionZkHosts.get(collectionName); } return null; } public Map<String,Class<? extends Expressible>> getFunctionNames(){ return functionNames; } public StreamFactory withFunctionName(String functionName, Class<? extends Expressible> clazz){ this.functionNames.put(functionName, clazz); return this; } public StreamExpressionParameter getOperand(StreamExpression expression, int parameterIndex){ if(null == expression.getParameters() || parameterIndex >= expression.getParameters().size()){ return null; } return expression.getParameters().get(parameterIndex); } public List<String> getValueOperands(StreamExpression expression){ return getOperandsOfType(expression, StreamExpressionValue.class).stream().map(item -> ((StreamExpressionValue) item).getValue()).collect(Collectors.toList()); } /** Given an expression, will return the value parameter at the given index, or null if doesn't exist */ public String getValueOperand(StreamExpression expression, int parameterIndex){ StreamExpressionParameter parameter = getOperand(expression, parameterIndex); if(null != parameter){ if(parameter instanceof StreamExpressionValue){ return ((StreamExpressionValue)parameter).getValue(); } else if(parameter instanceof StreamExpression) { return parameter.toString(); } } return null; } public List<StreamExpressionNamedParameter> getNamedOperands(StreamExpression expression){ List<StreamExpressionNamedParameter> namedParameters = new ArrayList<>(); for(StreamExpressionParameter parameter : getOperandsOfType(expression, StreamExpressionNamedParameter.class)){ namedParameters.add((StreamExpressionNamedParameter)parameter); } return namedParameters; } public StreamExpressionNamedParameter getNamedOperand(StreamExpression expression, String name){ List<StreamExpressionNamedParameter> namedParameters = getNamedOperands(expression); for(StreamExpressionNamedParameter param : namedParameters){ if(param.getName().equals(name)){ return param; } } return null; } public List<StreamExpression> getExpressionOperands(StreamExpression expression){ List<StreamExpression> namedParameters = new ArrayList<>(); for(StreamExpressionParameter parameter : getOperandsOfType(expression, StreamExpression.class)){ namedParameters.add((StreamExpression)parameter); } return namedParameters; } public List<StreamExpression> getExpressionOperands(StreamExpression expression, String functionName){ List<StreamExpression> namedParameters = new ArrayList<>(); for(StreamExpressionParameter parameter : getOperandsOfType(expression, StreamExpression.class)){ StreamExpression expressionOperand = (StreamExpression)parameter; if(expressionOperand.getFunctionName().equals(functionName)){ namedParameters.add(expressionOperand); } } return namedParameters; } public List<StreamExpressionParameter> getOperandsOfType(StreamExpression expression, Class ... clazzes){ List<StreamExpressionParameter> parameters = new ArrayList<>(); parameterLoop: for(StreamExpressionParameter parameter : expression.getParameters()){ for(Class clazz : clazzes){ if(!clazz.isAssignableFrom(parameter.getClass())){ continue parameterLoop; // go to the next parameter since this parameter cannot be assigned to at least one of the classes } } parameters.add(parameter); } return parameters; } public List<StreamExpression> getExpressionOperandsRepresentingTypes(StreamExpression expression, Class ... clazzes){ List<StreamExpression> matchingStreamExpressions = new ArrayList<>(); List<StreamExpression> allStreamExpressions = getExpressionOperands(expression); parameterLoop: for(StreamExpression streamExpression : allStreamExpressions){ if(functionNames.containsKey(streamExpression.getFunctionName())){ for(Class clazz : clazzes){ if(!clazz.isAssignableFrom(functionNames.get(streamExpression.getFunctionName()))){ continue parameterLoop; } } matchingStreamExpressions.add(streamExpression); } } return matchingStreamExpressions; } public boolean doesRepresentTypes(StreamExpression expression, Class ... clazzes){ if(functionNames.containsKey(expression.getFunctionName())){ for(Class clazz : clazzes){ if(!clazz.isAssignableFrom(functionNames.get(expression.getFunctionName()))){ return false; } } return true; } return false; } public int getIntOperand(StreamExpression expression, String paramName, Integer defaultValue) throws IOException{ StreamExpressionNamedParameter param = getNamedOperand(expression, paramName); if(null == param || null == param.getParameter() || !(param.getParameter() instanceof StreamExpressionValue)){ if(null != defaultValue){ return defaultValue; } throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting a single '%s' parameter of type integer but didn't find one",expression, paramName)); } String nStr = ((StreamExpressionValue)param.getParameter()).getValue(); try{ return Integer.parseInt(nStr); } catch(NumberFormatException e){ if(null != defaultValue){ return defaultValue; } throw new IOException(String.format(Locale.ROOT,"invalid expression %s - %s '%s' is not a valid integer.",expression, paramName, nStr)); } } public boolean getBooleanOperand(StreamExpression expression, String paramName, Boolean defaultValue) throws IOException{ StreamExpressionNamedParameter param = getNamedOperand(expression, paramName); if(null == param || null == param.getParameter() || !(param.getParameter() instanceof StreamExpressionValue)){ if(null != defaultValue){ return defaultValue; } throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting a single '%s' parameter of type boolean but didn't find one",expression, paramName)); } String nStr = ((StreamExpressionValue)param.getParameter()).getValue(); return Boolean.parseBoolean(nStr); } public TupleStream constructStream(String expressionClause) throws IOException { return constructStream(StreamExpressionParser.parse(expressionClause)); } public TupleStream constructStream(StreamExpression expression) throws IOException{ String function = expression.getFunctionName(); if(functionNames.containsKey(function)){ Class<? extends Expressible> clazz = functionNames.get(function); if(Expressible.class.isAssignableFrom(clazz) && TupleStream.class.isAssignableFrom(clazz)){ return (TupleStream)createInstance(functionNames.get(function), new Class[]{ StreamExpression.class, StreamFactory.class }, new Object[]{ expression, this}); } } throw new IOException(String.format(Locale.ROOT,"Invalid stream expression %s - function '%s' is unknown (not mapped to a valid TupleStream)", expression, expression.getFunctionName())); } public Metric constructMetric(String expressionClause) throws IOException { return constructMetric(StreamExpressionParser.parse(expressionClause)); } public Metric constructMetric(StreamExpression expression) throws IOException{ String function = expression.getFunctionName(); if(functionNames.containsKey(function)){ Class<? extends Expressible> clazz = functionNames.get(function); if(Expressible.class.isAssignableFrom(clazz) && Metric.class.isAssignableFrom(clazz)){ return (Metric)createInstance(functionNames.get(function), new Class[]{ StreamExpression.class, StreamFactory.class }, new Object[]{ expression, this}); } } throw new IOException(String.format(Locale.ROOT,"Invalid metric expression %s - function '%s' is unknown (not mapped to a valid Metric)", expression, expression.getFunctionName())); } public StreamComparator constructComparator(String comparatorString, Class comparatorType) throws IOException { if(comparatorString.contains(",")){ String[] parts = comparatorString.split(","); StreamComparator[] comps = new StreamComparator[parts.length]; for(int idx = 0; idx < parts.length; ++idx){ comps[idx] = constructComparator(parts[idx].trim(), comparatorType); } return new MultipleFieldComparator(comps); } else if(comparatorString.contains("=")){ // expected format is "left=right order" String[] parts = comparatorString.split("[ =]"); if(parts.length < 3){ throw new IOException(String.format(Locale.ROOT,"Invalid comparator expression %s - expecting 'left=right order'",comparatorString)); } String leftFieldName = null; String rightFieldName = null; String order = null; for(String part : parts){ // skip empty if(null == part || 0 == part.trim().length()){ continue; } // assign each in order if(null == leftFieldName){ leftFieldName = part.trim(); } else if(null == rightFieldName){ rightFieldName = part.trim(); } else { order = part.trim(); break; // we're done, stop looping } } if(null == leftFieldName || null == rightFieldName || null == order){ throw new IOException(String.format(Locale.ROOT,"Invalid comparator expression %s - expecting 'left=right order'",comparatorString)); } return (StreamComparator)createInstance(comparatorType, new Class[]{ String.class, String.class, ComparatorOrder.class }, new Object[]{ leftFieldName, rightFieldName, ComparatorOrder.fromString(order) }); } else{ // expected format is "field order" String[] parts = comparatorString.split(" "); if(2 != parts.length){ throw new IOException(String.format(Locale.ROOT,"Invalid comparator expression %s - expecting 'field order'",comparatorString)); } String fieldName = parts[0].trim(); String order = parts[1].trim(); return (StreamComparator)createInstance(comparatorType, new Class[]{ String.class, ComparatorOrder.class }, new Object[]{ fieldName, ComparatorOrder.fromString(order) }); } } public StreamEqualitor constructEqualitor(String equalitorString, Class equalitorType) throws IOException { if(equalitorString.contains(",")){ String[] parts = equalitorString.split(","); StreamEqualitor[] eqs = new StreamEqualitor[parts.length]; for(int idx = 0; idx < parts.length; ++idx){ eqs[idx] = constructEqualitor(parts[idx].trim(), equalitorType); } return new MultipleFieldEqualitor(eqs); } else{ String leftFieldName; String rightFieldName; if(equalitorString.contains("=")){ String[] parts = equalitorString.split("="); if(2 != parts.length){ throw new IOException(String.format(Locale.ROOT,"Invalid equalitor expression %s - expecting fieldName=fieldName",equalitorString)); } leftFieldName = parts[0].trim(); rightFieldName = parts[1].trim(); } else{ leftFieldName = rightFieldName = equalitorString.trim(); } return (StreamEqualitor)createInstance(equalitorType, new Class[]{ String.class, String.class }, new Object[]{ leftFieldName, rightFieldName }); } } public Metric constructOperation(String expressionClause) throws IOException { return constructMetric(StreamExpressionParser.parse(expressionClause)); } public StreamOperation constructOperation(StreamExpression expression) throws IOException{ String function = expression.getFunctionName(); if(functionNames.containsKey(function)){ Class<? extends Expressible> clazz = functionNames.get(function); if(Expressible.class.isAssignableFrom(clazz) && StreamOperation.class.isAssignableFrom(clazz)){ return (StreamOperation)createInstance(functionNames.get(function), new Class[]{ StreamExpression.class, StreamFactory.class }, new Object[]{ expression, this}); } } throw new IOException(String.format(Locale.ROOT,"Invalid operation expression %s - function '%s' is unknown (not mapped to a valid StreamOperation)", expression, expression.getFunctionName())); } public StreamEvaluator constructEvaluator(String expressionClause) throws IOException { return constructEvaluator(StreamExpressionParser.parse(expressionClause)); } public StreamEvaluator constructEvaluator(StreamExpression expression) throws IOException{ String function = expression.getFunctionName(); if(functionNames.containsKey(function)){ Class<? extends Expressible> clazz = functionNames.get(function); if(Expressible.class.isAssignableFrom(clazz) && StreamEvaluator.class.isAssignableFrom(clazz)){ return (StreamEvaluator)createInstance(functionNames.get(function), new Class[]{ StreamExpression.class, StreamFactory.class }, new Object[]{ expression, this}); } } throw new IOException(String.format(Locale.ROOT,"Invalid evaluator expression %s - function '%s' is unknown (not mapped to a valid StreamEvaluator)", expression, expression.getFunctionName())); } public boolean isStream(StreamExpression expression) throws IOException{ String function = expression.getFunctionName(); if(functionNames.containsKey(function)){ Class<? extends Expressible> clazz = functionNames.get(function); if(Expressible.class.isAssignableFrom(clazz) && TupleStream.class.isAssignableFrom(clazz)){ return true; } } return false; } public boolean isEvaluator(StreamExpression expression) throws IOException{ String function = expression.getFunctionName(); if(functionNames.containsKey(function)){ Class<? extends Expressible> clazz = functionNames.get(function); if(Expressible.class.isAssignableFrom(clazz) && StreamEvaluator.class.isAssignableFrom(clazz)){ return true; } } return false; } public <T> T createInstance(Class<T> clazz, Class<?>[] paramTypes, Object[] params) throws IOException{ Constructor<T> ctor; try { ctor = clazz.getConstructor(paramTypes); return ctor.newInstance(params); } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { if(null != e.getMessage()){ throw new IOException(String.format(Locale.ROOT,"Unable to construct instance of %s caused by %s", clazz.getName(), e.getMessage()),e); } else{ throw new IOException(String.format(Locale.ROOT,"Unable to construct instance of %s", clazz.getName()),e); } } } public String getFunctionName(Class<? extends Expressible> clazz) throws IOException{ for(Entry<String,Class<? extends Expressible>> entry : functionNames.entrySet()){ if(entry.getValue() == clazz){ return entry.getKey(); } } throw new IOException(String.format(Locale.ROOT, "Unable to find function name for class '%s'", clazz.getName())); } public Object constructPrimitiveObject(String original){ String lower = original.trim().toLowerCase(Locale.ROOT); if("null".equals(lower)){ return null; } if("true".equals(lower) || "false".equals(lower)){ return Boolean.parseBoolean(lower); } try{ return Long.valueOf(original); } catch(Exception ignored){}; try{ if (original.matches(".{1,8}")){ return Float.valueOf(original); }} catch(Exception ignored){}; try{ if (original.matches(".{1,17}")){ return Double.valueOf(original); }} catch(Exception ignored){}; // is a string return original; } }