diff --git a/lucene/analysis/kuromoji/src/java/org/apache/lucene/analysis/ja/dict/UserDictionary.java b/lucene/analysis/kuromoji/src/java/org/apache/lucene/analysis/ja/dict/UserDictionary.java index 16b0721ca80..84890032514 100644 --- a/lucene/analysis/kuromoji/src/java/org/apache/lucene/analysis/ja/dict/UserDictionary.java +++ b/lucene/analysis/kuromoji/src/java/org/apache/lucene/analysis/ja/dict/UserDictionary.java @@ -29,8 +29,8 @@ import java.util.TreeMap; import org.apache.lucene.analysis.ja.util.CSVUtil; import org.apache.lucene.util.IntsRefBuilder; -import org.apache.lucene.util.fst.FSTCompiler; import org.apache.lucene.util.fst.FST; +import org.apache.lucene.util.fst.FSTCompiler; import org.apache.lucene.util.fst.PositiveIntOutputs; /** diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt index c9f5f1d7cc8..cd562940f09 100644 --- a/solr/CHANGES.txt +++ b/solr/CHANGES.txt @@ -161,6 +161,8 @@ Improvements * SOLR-14095: Replace Java serialization with Javabin in the Overseer queues (Tomás Fernández Löbbe) +* SOLR-14125: Make plugins work with packages (noble) + Optimizations --------------------- (No changes) diff --git a/solr/core/src/java/org/apache/solr/handler/GraphHandler.java b/solr/core/src/java/org/apache/solr/handler/GraphHandler.java index ed5ae0aeec1..621a4db3bdc 100644 --- a/solr/core/src/java/org/apache/solr/handler/GraphHandler.java +++ b/solr/core/src/java/org/apache/solr/handler/GraphHandler.java @@ -19,6 +19,7 @@ package org.apache.solr.handler; import java.io.IOException; import java.lang.invoke.MethodHandles; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -27,7 +28,9 @@ import java.util.Map.Entry; import org.apache.solr.client.solrj.io.Tuple; import org.apache.solr.client.solrj.io.comp.StreamComparator; import org.apache.solr.client.solrj.io.graph.Traversal; -import org.apache.solr.client.solrj.io.stream.*; +import org.apache.solr.client.solrj.io.stream.ExceptionStream; +import org.apache.solr.client.solrj.io.stream.StreamContext; +import org.apache.solr.client.solrj.io.stream.TupleStream; import org.apache.solr.client.solrj.io.stream.expr.DefaultStreamFactory; import org.apache.solr.client.solrj.io.stream.expr.Explanation; import org.apache.solr.client.solrj.io.stream.expr.Expressible; @@ -38,6 +41,8 @@ import org.apache.solr.common.params.ModifiableSolrParams; import org.apache.solr.common.params.SolrParams; import org.apache.solr.common.util.NamedList; import org.apache.solr.core.CoreContainer; +import org.apache.solr.core.PluginInfo; +import org.apache.solr.core.SolrConfig; import org.apache.solr.core.SolrCore; import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.response.SolrQueryResponse; @@ -90,11 +95,21 @@ public class GraphHandler extends RequestHandlerBase implements SolrCoreAware, P Object functionMappingsObj = initArgs.get("streamFunctions"); if(null != functionMappingsObj){ NamedList functionMappings = (NamedList)functionMappingsObj; - for(Entry functionMapping : functionMappings){ - Class clazz = core.getResourceLoader().findClass((String)functionMapping.getValue(), - Expressible.class); - streamFactory.withFunctionName(functionMapping.getKey(), clazz); + for(Entry functionMapping : functionMappings) { + String key = functionMapping.getKey(); + PluginInfo pluginInfo = new PluginInfo(key, Collections.singletonMap("class", functionMapping.getValue())); + + if (pluginInfo.pkgName == null) { + Class clazz = core.getResourceLoader().findClass((String) functionMapping.getValue(), + Expressible.class); + streamFactory.withFunctionName(key, clazz); + } else { + StreamHandler.ExpressibleHolder holder = new StreamHandler.ExpressibleHolder(pluginInfo, core, SolrConfig.classVsSolrPluginInfo.get(Expressible.class)); + streamFactory.withFunctionName(key, () -> holder.getClazz()); + } + } + } } diff --git a/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java b/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java index 004da318ae3..09f2778c2f8 100644 --- a/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java +++ b/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java @@ -45,6 +45,7 @@ import org.apache.solr.client.solrj.SolrClient; import org.apache.solr.client.solrj.SolrRequest; import org.apache.solr.client.solrj.SolrResponse; import org.apache.solr.client.solrj.impl.HttpSolrClient; +import org.apache.solr.client.solrj.io.stream.expr.Expressible; import org.apache.solr.cloud.ZkController; import org.apache.solr.cloud.ZkSolrResourceLoader; import org.apache.solr.common.SolrException; @@ -588,9 +589,17 @@ public class SolrConfigHandler extends RequestHandlerBase implements SolrCoreAwa private boolean verifyClass(CommandOperation op, String clz, Class expected) { if (clz == null) return true; if (!"true".equals(String.valueOf(op.getStr("runtimeLib", null)))) { + PluginInfo info = new PluginInfo(SolrRequestHandler.TYPE, op.getDataMap()); //this is not dynamically loaded so we can verify the class right away try { - req.getCore().createInitInstance(new PluginInfo(SolrRequestHandler.TYPE, op.getDataMap()), expected, clz, ""); + if(expected == Expressible.class) { + SolrResourceLoader resourceLoader = info.pkgName == null ? + req.getCore().getResourceLoader() : + req.getCore().getResourceLoader(info.pkgName); + resourceLoader.findClass(info.className, expected); + } else { + req.getCore().createInitInstance(info, expected, clz, ""); + } } catch (Exception e) { log.error("Error checking plugin : ", e); op.addError(e.getMessage()); diff --git a/solr/core/src/java/org/apache/solr/handler/StreamHandler.java b/solr/core/src/java/org/apache/solr/handler/StreamHandler.java index 5d8bd5ca76b..9387cc5729b 100644 --- a/solr/core/src/java/org/apache/solr/handler/StreamHandler.java +++ b/solr/core/src/java/org/apache/solr/handler/StreamHandler.java @@ -33,7 +33,10 @@ import org.apache.solr.client.solrj.io.ModelCache; import org.apache.solr.client.solrj.io.SolrClientCache; import org.apache.solr.client.solrj.io.Tuple; import org.apache.solr.client.solrj.io.comp.StreamComparator; -import org.apache.solr.client.solrj.io.stream.*; +import org.apache.solr.client.solrj.io.stream.DaemonStream; +import org.apache.solr.client.solrj.io.stream.ExceptionStream; +import org.apache.solr.client.solrj.io.stream.StreamContext; +import org.apache.solr.client.solrj.io.stream.TupleStream; import org.apache.solr.client.solrj.io.stream.expr.Explanation; import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType; import org.apache.solr.client.solrj.io.stream.expr.Expressible; @@ -44,6 +47,7 @@ import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParser; import org.apache.solr.client.solrj.io.stream.expr.StreamFactory; import org.apache.solr.client.solrj.routing.RequestReplicaListTransformerGenerator; import org.apache.solr.cloud.ZkController; +import org.apache.solr.common.MapWriter; import org.apache.solr.common.SolrException; import org.apache.solr.common.cloud.ZkStateReader; import org.apache.solr.common.params.CommonParams; @@ -52,7 +56,10 @@ import org.apache.solr.common.params.SolrParams; import org.apache.solr.core.CloseHook; import org.apache.solr.core.CoreContainer; import org.apache.solr.core.PluginInfo; +import org.apache.solr.core.SolrConfig; import org.apache.solr.core.SolrCore; +import org.apache.solr.pkg.PackageLoader; +import org.apache.solr.pkg.PackagePluginHolder; import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.response.SolrQueryResponse; import org.apache.solr.security.AuthorizationContext; @@ -87,7 +94,7 @@ public class StreamHandler extends RequestHandlerBase implements SolrCoreAware, private SolrDefaultStreamFactory streamFactory = new SolrDefaultStreamFactory(); private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); private String coreName; - private Map daemons = Collections.synchronizedMap(new HashMap()); + private Map daemons = Collections.synchronizedMap(new HashMap()); @Override public PermissionNameProvider.Name getPermissionName(AuthorizationContext request) { @@ -118,8 +125,14 @@ public class StreamHandler extends RequestHandlerBase implements SolrCoreAware, // This pulls all the overrides and additions from the config List pluginInfos = core.getSolrConfig().getPluginInfos(Expressible.class.getName()); for (PluginInfo pluginInfo : pluginInfos) { - Class clazz = core.getMemClassLoader().findClass(pluginInfo.className, Expressible.class); - streamFactory.withFunctionName(pluginInfo.name, clazz); + if (pluginInfo.pkgName != null) { + ExpressibleHolder holder = new ExpressibleHolder(pluginInfo, core, SolrConfig.classVsSolrPluginInfo.get(Expressible.class)); + streamFactory.withFunctionName(pluginInfo.name, + () -> holder.getClazz()); + } else { + Class clazz = core.getMemClassLoader().findClass(pluginInfo.className, Expressible.class); + streamFactory.withFunctionName(pluginInfo.name, clazz); + } } core.addCloseHook(new CloseHook() { @@ -135,6 +148,24 @@ public class StreamHandler extends RequestHandlerBase implements SolrCoreAware, }); } + public static class ExpressibleHolder extends PackagePluginHolder { + private Class clazz; + + public ExpressibleHolder(PluginInfo info, SolrCore core, SolrConfig.SolrPluginInfo pluginMeta) { + super(info, core, pluginMeta); + } + + public Class getClazz() { + return clazz; + } + + @Override + protected void initNewInstance(PackageLoader.Package.Version newest) { + clazz = newest.getLoader().findClass(pluginInfo.className, Expressible.class); + } + + } + public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception { SolrParams params = req.getParams(); params = adjustParams(params); @@ -220,6 +251,10 @@ public class StreamHandler extends RequestHandlerBase implements SolrCoreAware, private void handleAdmin(SolrQueryRequest req, SolrQueryResponse rsp, SolrParams params) { String action = params.get("action").toLowerCase(Locale.ROOT).trim(); + if ("plugins".equals(action)) { + rsp.add("plugins", (MapWriter) ew -> streamFactory.getFunctionNames().forEach((s, classSupplier) -> ew.putNoEx(s, classSupplier.get().getName()))); + return; + } if ("list".equals(action)) { Collection vals = daemons.values(); @@ -255,10 +290,10 @@ public class StreamHandler extends RequestHandlerBase implements SolrCoreAware, rsp.add("result-set", new DaemonResponseStream("Deamon:" + id + " killed on " + coreName)); break; - default: - rsp.add("result-set", new DaemonResponseStream("Deamon:" + id + " action '" - + action + "' not recognized on " + coreName)); - break; + default: + rsp.add("result-set", new DaemonResponseStream("Deamon:" + id + " action '" + + action + "' not recognized on " + coreName)); + break; } } @@ -288,11 +323,14 @@ public class StreamHandler extends RequestHandlerBase implements SolrCoreAware, return null; } - public void close() {} + public void close() { + } - public void open() {} + public void open() { + } - public void setStreamContext(StreamContext context) {} + public void setStreamContext(StreamContext context) { + } public List children() { return null; @@ -335,11 +373,14 @@ public class StreamHandler extends RequestHandlerBase implements SolrCoreAware, return null; } - public void close() {} + public void close() { + } - public void open() {} + public void open() { + } - public void setStreamContext(StreamContext context) {} + public void setStreamContext(StreamContext context) { + } public List children() { return null; @@ -378,11 +419,14 @@ public class StreamHandler extends RequestHandlerBase implements SolrCoreAware, return null; } - public void close() {} + public void close() { + } - public void open() {} + public void open() { + } - public void setStreamContext(StreamContext context) {} + public void setStreamContext(StreamContext context) { + } public List children() { return null; @@ -462,9 +506,9 @@ public class StreamHandler extends RequestHandlerBase implements SolrCoreAware, } } - private Map> getCollectionShards(SolrParams params) { + private Map> getCollectionShards(SolrParams params) { - Map> collectionShards = new HashMap(); + Map> collectionShards = new HashMap(); Iterator paramsIt = params.getParameterNamesIterator(); while (paramsIt.hasNext()) { String param = paramsIt.next(); diff --git a/solr/core/src/java/org/apache/solr/pkg/PackagePluginHolder.java b/solr/core/src/java/org/apache/solr/pkg/PackagePluginHolder.java index 63facdec0d3..c6ddd4fe758 100644 --- a/solr/core/src/java/org/apache/solr/pkg/PackagePluginHolder.java +++ b/solr/core/src/java/org/apache/solr/pkg/PackagePluginHolder.java @@ -104,12 +104,17 @@ public class PackagePluginHolder extends PluginBag.PluginHolder { log.info("loading plugin: {} -> {} using package {}:{}", pluginInfo.type, pluginInfo.name, pkg.name(), newest.getVersion()); + initNewInstance(newest); + pkgVersion = newest; + + } + + protected void initNewInstance(PackageLoader.Package.Version newest) { Object instance = SolrCore.createInstance(pluginInfo.className, pluginMeta.clazz, pluginMeta.getCleanTag(), core, newest.getLoader()); PluginBag.initInstance(instance, pluginInfo); T old = inst; inst = (T) instance; - pkgVersion = newest; if (old instanceof AutoCloseable) { AutoCloseable closeable = (AutoCloseable) old; try { diff --git a/solr/core/src/test-files/runtimecode/expressible.jar.bin b/solr/core/src/test-files/runtimecode/expressible.jar.bin new file mode 100644 index 00000000000..50d61b9cf3a Binary files /dev/null and b/solr/core/src/test-files/runtimecode/expressible.jar.bin differ diff --git a/solr/core/src/test-files/runtimecode/sig.txt b/solr/core/src/test-files/runtimecode/sig.txt index f354b5f8933..74bb94259ab 100644 --- a/solr/core/src/test-files/runtimecode/sig.txt +++ b/solr/core/src/test-files/runtimecode/sig.txt @@ -65,6 +65,10 @@ openssl dgst -sha1 -sign ../cryptokeys/priv_key512.pem testurp_v2.jar.bin | open P/ptFXRvQMd4oKPvadSpd+A9ffwY3gcex5GVFVRy3df0/OF8XT5my8rQz7FZva+2ORbWxdXS8NKwNrbPVHLGXw== +openssl dgst -sha1 -sign ../cryptokeys/priv_key512.pem expressible.jar.bin | openssl enc -base64 | openssl enc -base64 | tr -d \\n | sed + +ZOT11arAiPmPZYOHzqodiNnxO9pRyRozWZEBX8XGjU1/HJptFnZK+DI7eXnUtbNaMcbXE2Ze8hh4M/eGyhY8BQ== + ====================sha512==================== openssl dgst -sha512 runtimelibs.jar.bin @@ -95,6 +99,11 @@ openssl dgst -sha512 testurp_v1.jar.bin openssl dgst -sha512 testurp_v2.jar.bin 5c4c0c454a032916e48a1c14a0fecbd6658658a66aedec5168b7222f2e3c0c63fbe09637238a9325ce2e95a2c8521834397a97701ead46c681aa20c9fccb6654 + +openssl dgst -sha512 expressible.jar.bin + +3474a1414c8329c71ef5db2d3eb6e870363bdd7224a836aab561dccf5e8bcee4974ac799e72398c7e0b0c01972bab1c7454c8a4e791a8865bb676c0440627388 + =============sha256============================ openssl dgst -sha256 runtimelibs.jar.bin diff --git a/solr/core/src/test/org/apache/solr/pkg/TestPackages.java b/solr/core/src/test/org/apache/solr/pkg/TestPackages.java index baa8a99b02d..d37fdf4af70 100644 --- a/solr/core/src/test/org/apache/solr/pkg/TestPackages.java +++ b/solr/core/src/test/org/apache/solr/pkg/TestPackages.java @@ -92,6 +92,7 @@ public class TestPackages extends SolrCloudTestCase { String FILE3 = "/mypkg/runtimelibs_v3.jar"; String URP1 = "/mypkg/testurpv1.jar"; String URP2 = "/mypkg/testurpv2.jar"; + String EXPR1 = "/mypkg/expressible.jar"; String COLLECTION_NAME = "testPluginLoadingColl"; byte[] derFile = readFile("cryptokeys/pub_key512.der"); cluster.getZkClient().makePath("/keys/exe", true); @@ -102,10 +103,13 @@ public class TestPackages extends SolrCloudTestCase { postFileAndWait(cluster, "runtimecode/testurp_v1.jar.bin", URP1, "h6UmMzuPqu4hQFGLBMJh/6kDSEXpJlgLsQDXx0KuxXWkV5giilRP57K3towiJRh2J+rqihqIghNCi3YgzgUnWQ=="); + postFileAndWait(cluster, "runtimecode/expressible.jar.bin", EXPR1, + "ZOT11arAiPmPZYOHzqodiNnxO9pRyRozWZEBX8XGjU1/HJptFnZK+DI7eXnUtbNaMcbXE2Ze8hh4M/eGyhY8BQ=="); + Package.AddVersion add = new Package.AddVersion(); add.version = "1.0"; add.pkg = "mypkg"; - add.files = Arrays.asList(new String[]{FILE1, URP1}); + add.files = Arrays.asList(new String[]{FILE1, URP1, EXPR1}); V2Request req = new V2Request.Builder("/cluster/package") .forceV2(true) .withMethod(SolrRequest.METHOD.POST) @@ -134,7 +138,8 @@ public class TestPackages extends SolrCloudTestCase { "'create-requesthandler' : { 'name' : '/runtime', 'class': 'mypkg:org.apache.solr.core.RuntimeLibReqHandler' }," + "'create-searchcomponent' : { 'name' : 'get', 'class': 'mypkg:org.apache.solr.core.RuntimeLibSearchComponent' }," + "'create-queryResponseWriter' : { 'name' : 'json1', 'class': 'mypkg:org.apache.solr.core.RuntimeLibResponseWriter' }" + - "'create-updateProcessor' : { 'name' : 'myurp', 'class': 'mypkg:org.apache.solr.update.TestVersionedURP' }" + + "'create-updateProcessor' : { 'name' : 'myurp', 'class': 'mypkg:org.apache.solr.update.TestVersionedURP' }," + + " create-expressible: {name: mincopy , class: 'mypkg:org.apache.solr.client.solrj.io.stream.metrics.MinCopyMetric'}" + "}"; cluster.getSolrClient().request(new ConfigRequest(payload) { @Override @@ -159,6 +164,20 @@ public class TestPackages extends SolrCloudTestCase { COLLECTION_NAME, "updateProcessor", "myurp", "mypkg", "1.0" ); + verifyCmponent(cluster.getSolrClient(), + COLLECTION_NAME, "expressible", "mincopy", + "mypkg", "1.0" ); + + TestDistribPackageStore.assertResponseValues(10, + cluster.getSolrClient() , + new GenericSolrRequest(SolrRequest.METHOD.GET, + "/stream", new MapSolrParams((Map) Utils.makeMap("collection", COLLECTION_NAME, + WT, JAVABIN, + "action", "plugins" + ))), Utils.makeMap( + ":plugins:mincopy", "org.apache.solr.client.solrj.io.stream.metrics.MinCopyMetric" + )); + UpdateRequest ur = new UpdateRequest(); ur.add(new SolrInputDocument("id", "1")); ur.setParam("processor", "myurp"); @@ -192,7 +211,7 @@ public class TestPackages extends SolrCloudTestCase { "P/ptFXRvQMd4oKPvadSpd+A9ffwY3gcex5GVFVRy3df0/OF8XT5my8rQz7FZva+2ORbWxdXS8NKwNrbPVHLGXw=="); //add the version using package API add.version = "1.1"; - add.files = Arrays.asList(new String[]{FILE2,URP2}); + add.files = Arrays.asList(new String[]{FILE2,URP2, EXPR1}); req.process(cluster.getSolrClient()); verifyCmponent(cluster.getSolrClient(), @@ -222,7 +241,7 @@ public class TestPackages extends SolrCloudTestCase { "a400n4T7FT+2gM0SC6+MfSOExjud8MkhTSFylhvwNjtWwUgKdPFn434Wv7Qc4QEqDVLhQoL3WqYtQmLPti0G4Q=="); add.version = "2.1"; - add.files = Arrays.asList(new String[]{FILE3, URP2}); + add.files = Arrays.asList(new String[]{FILE3, URP2, EXPR1}); req.process(cluster.getSolrClient()); //now let's verify that the classes are updated @@ -304,7 +323,7 @@ public class TestPackages extends SolrCloudTestCase { }.process(cluster.getSolrClient()) ; add.version = "2.1"; - add.files = Arrays.asList(new String[]{FILE3, URP2}); + add.files = Arrays.asList(new String[]{FILE3, URP2, EXPR1}); req.process(cluster.getSolrClient()); //the collections mypkg is set to use version 1.1 @@ -368,6 +387,21 @@ public class TestPackages extends SolrCloudTestCase { } } + /* new V2Request.Builder("/c/"+COLLECTIONORALIAS+"/config").withMethod(SolrRequest.METHOD.POST) + .withPayload("{add-expressible: {name: mincopy , class: org.apache.solr.client.solrj.io.stream.metrics.MinCopyMetric}}") + .build().process(cluster.getSolrClient()); + + ModifiableSolrParams _params = new ModifiableSolrParams(); + QueryRequest query = new QueryRequest(new MapSolrParams("action","plugins", "collection", COLLECTIONORALIAS, "wt", "javabin")); + query.setPath("/stream"); + NamedList rsp = cluster.getSolrClient().request(query); + assertEquals("org.apache.solr.client.solrj.io.stream.metrics.MinCopyMetric", rsp._getStr("/plugins/mincopy", null)); + _params = new ModifiableSolrParams(); + query = new QueryRequest(new MapSolrParams("componentName","mincopy", "meta" ,"true", "collection", COLLECTIONORALIAS, "wt", "javabin")); + query.setPath("/config/expressible"); + rsp = cluster.getSolrClient().request(query); + + System.out.println();*/ private void executeReq(String uri, JettySolrRunner jetty, Utils.InputStreamConsumer parser, Map expected) throws Exception { try(HttpSolrClient client = (HttpSolrClient) jetty.newClient()){ @@ -390,7 +424,6 @@ public class TestPackages extends SolrCloudTestCase { "componentName", componentName, "meta", "true")); - String s = "queryResponseWriter"; GenericSolrRequest req1 = new GenericSolrRequest(SolrRequest.METHOD.GET, "/config/" + componentType, params); TestDistribPackageStore.assertResponseValues(10, diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/stream/expr/StreamFactory.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/stream/expr/StreamFactory.java index 0ba5d5c3e42..4e176dd212c 100644 --- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/stream/expr/StreamFactory.java +++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/stream/expr/StreamFactory.java @@ -21,11 +21,13 @@ import java.io.Serializable; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; +import java.util.function.Supplier; import java.util.stream.Collectors; import org.apache.solr.client.solrj.io.comp.ComparatorOrder; @@ -44,7 +46,7 @@ import org.apache.solr.client.solrj.io.stream.metrics.Metric; public class StreamFactory implements Serializable { private transient HashMap collectionZkHosts; - private transient HashMap> functionNames; + private transient HashMap>> functionNames; private transient String defaultZkHost; private transient String defaultCollection; @@ -79,14 +81,20 @@ public class StreamFactory implements Serializable { return null; } - public Map> getFunctionNames(){ - return functionNames; + public Map>> getFunctionNames() { + return Collections.unmodifiableMap(functionNames); } public StreamFactory withFunctionName(String functionName, Class clazz){ + this.functionNames.put(functionName, () -> clazz); + return this; + } + + public StreamFactory withFunctionName(String functionName, Supplier< Class> 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; @@ -173,14 +181,15 @@ public class StreamFactory implements Serializable { List allStreamExpressions = getExpressionOperands(expression); parameterLoop: - for(StreamExpression streamExpression : allStreamExpressions){ - if(functionNames.containsKey(streamExpression.getFunctionName())){ - for(Class clazz : clazzes){ - if(!clazz.isAssignableFrom(functionNames.get(streamExpression.getFunctionName()))){ + for(StreamExpression streamExpression : allStreamExpressions) { + Supplier> classSupplier = functionNames.get(streamExpression.getFunctionName()); + if (classSupplier != null) { + for (Class clazz : clazzes) { + if (!clazz.isAssignableFrom(classSupplier.get())) { continue parameterLoop; } } - + matchingStreamExpressions.add(streamExpression); } } @@ -189,9 +198,10 @@ public class StreamFactory implements Serializable { } public boolean doesRepresentTypes(StreamExpression expression, Class ... clazzes){ - if(functionNames.containsKey(expression.getFunctionName())){ + Supplier> classSupplier = functionNames.get(expression.getFunctionName()); + if(classSupplier != null){ for(Class clazz : clazzes){ - if(!clazz.isAssignableFrom(functionNames.get(expression.getFunctionName()))){ + if(!clazz.isAssignableFrom(classSupplier.get())){ return false; } } @@ -203,7 +213,7 @@ public class StreamFactory implements Serializable { 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; @@ -241,10 +251,12 @@ public class StreamFactory implements Serializable { } public TupleStream constructStream(StreamExpression expression) throws IOException{ String function = expression.getFunctionName(); - if(functionNames.containsKey(function)){ - Class clazz = functionNames.get(function); + Supplier> classSupplier = functionNames.get(function); + + if(classSupplier != null){ + Class clazz = classSupplier.get(); 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}); + return (TupleStream)createInstance(clazz, new Class[]{ StreamExpression.class, StreamFactory.class }, new Object[]{ expression, this}); } } @@ -256,10 +268,11 @@ public class StreamFactory implements Serializable { } public Metric constructMetric(StreamExpression expression) throws IOException{ String function = expression.getFunctionName(); - if(functionNames.containsKey(function)){ - Class clazz = functionNames.get(function); + Supplier> classSupplier = functionNames.get(function); + if(classSupplier != null){ + Class clazz = classSupplier.get(); 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}); + return (Metric)createInstance(clazz, new Class[]{ StreamExpression.class, StreamFactory.class }, new Object[]{ expression, this}); } } @@ -356,16 +369,18 @@ public class StreamFactory implements Serializable { public Metric constructOperation(String expressionClause) throws IOException { return constructMetric(StreamExpressionParser.parse(expressionClause)); } - public StreamOperation constructOperation(StreamExpression expression) throws IOException{ + + public StreamOperation constructOperation(StreamExpression expression) throws IOException { String function = expression.getFunctionName(); - if(functionNames.containsKey(function)){ - Class 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}); + Supplier> classSupplier = functionNames.get(function); + if (classSupplier != null) { + Class clazz = classSupplier.get(); + if (Expressible.class.isAssignableFrom(clazz) && StreamOperation.class.isAssignableFrom(clazz)) { + return (StreamOperation) createInstance(clazz, 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())); + + 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 org.apache.solr.client.solrj.io.eval.StreamEvaluator constructEvaluator(String expressionClause) throws IOException { @@ -373,33 +388,24 @@ public class StreamFactory implements Serializable { } public org.apache.solr.client.solrj.io.eval.StreamEvaluator constructEvaluator(StreamExpression expression) throws IOException{ String function = expression.getFunctionName(); - if(functionNames.containsKey(function)){ - Class clazz = functionNames.get(function); + Supplier> classSupplier = functionNames.get(function); + + if(classSupplier != null){ + Class clazz = classSupplier.get(); if(Expressible.class.isAssignableFrom(clazz) && StreamEvaluator.class.isAssignableFrom(clazz)){ - return (org.apache.solr.client.solrj.io.eval.StreamEvaluator)createInstance(functionNames.get(function), new Class[]{ StreamExpression.class, StreamFactory.class }, new Object[]{ expression, this}); + return (org.apache.solr.client.solrj.io.eval.StreamEvaluator)createInstance(clazz, 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{ + public boolean isStream(StreamExpression expression) throws IOException { String function = expression.getFunctionName(); - if(functionNames.containsKey(function)){ - Class 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 clazz = functionNames.get(function); - if(Expressible.class.isAssignableFrom(clazz) && StreamEvaluator.class.isAssignableFrom(clazz)){ + Supplier> classSupplier = functionNames.get(function); + if (classSupplier != null) { + Class clazz = classSupplier.get(); + if (Expressible.class.isAssignableFrom(clazz) && TupleStream.class.isAssignableFrom(clazz)) { return true; } } @@ -407,35 +413,48 @@ public class StreamFactory implements Serializable { return false; } - public T createInstance(Class clazz, Class[] paramTypes, Object[] params) throws IOException{ + public boolean isEvaluator(StreamExpression expression) throws IOException { + String function = expression.getFunctionName(); + Supplier> classSupplier = functionNames.get(function); + if (classSupplier != null) { + Class clazz = classSupplier.get(); + if (Expressible.class.isAssignableFrom(clazz) && StreamEvaluator.class.isAssignableFrom(clazz)) { + return true; + } + } + + return false; + } + + public T createInstance(Class clazz, Class[] paramTypes, Object[] params) throws IOException { Constructor 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); + 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 clazz) throws IOException{ - for(Entry> entry : functionNames.entrySet()){ - if(entry.getValue() == clazz){ + + public String getFunctionName(Class clazz) throws IOException { + for (Entry>> entry : functionNames.entrySet()) { + if (entry.getValue().get() == 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){}; diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/io/TestLang.java b/solr/solrj/src/test/org/apache/solr/client/solrj/io/TestLang.java index 7e510f43bc5..025f6b23619 100644 --- a/solr/solrj/src/test/org/apache/solr/client/solrj/io/TestLang.java +++ b/solr/solrj/src/test/org/apache/solr/client/solrj/io/TestLang.java @@ -20,6 +20,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Supplier; import org.apache.lucene.util.LuceneTestCase; import org.apache.lucene.util.LuceneTestCase.Slow; @@ -94,7 +95,7 @@ public class TestLang extends SolrTestCase { } StreamFactory factory = new StreamFactory(); Lang.register(factory); - Map> registeredFunctions = factory.getFunctionNames(); + Map>> registeredFunctions = factory.getFunctionNames(); //Check that each function that is expected is registered. for(String func : functions) {