mirror of
https://github.com/elastic/logstash.git
synced 2025-04-23 22:27:21 -04:00
Fix Javafier for MapJavaProxy, ArrayJavaProxy, ConcreteJavaProxy. (#5474)
* Fix Javafier for MapJavaProxy, ArrayJavaProxy, ConcreteJavaProxy. + JUnit tests + Rspec test * fix splat imports * use a method instead of switch case code and throw an error if we don't know how to handle the wrapped type. * Add hand crafted map to use in rspec from Java directly and rspec test * changes for review comments.
This commit is contained in:
parent
f62463421d
commit
c937160364
4 changed files with 240 additions and 27 deletions
|
@ -5,6 +5,7 @@ require "logstash/util"
|
|||
require "logstash/event"
|
||||
require "json"
|
||||
require "java"
|
||||
# java_import 'com.logstash.Util'
|
||||
|
||||
TIMESTAMP = "@timestamp"
|
||||
|
||||
|
@ -112,6 +113,32 @@ describe LogStash::Event do
|
|||
expect(e.get("foo")).to be_kind_of(Bignum)
|
||||
expect(e.get("foo")).to eq(-9223372036854776000)
|
||||
end
|
||||
|
||||
it "should set XXJavaProxy Jackson crafted" do
|
||||
proxy = com.logstash.Util.getMapFixtureJackson()
|
||||
# proxy is {"string": "foo", "int": 42, "float": 42.42, "array": ["bar","baz"], "hash": {"string":"quux"} }
|
||||
e = LogStash::Event.new()
|
||||
e.set("[proxy]", proxy)
|
||||
expect(e.get("[proxy][string]")).to eql("foo")
|
||||
expect(e.get("[proxy][int]")).to eql(42)
|
||||
expect(e.get("[proxy][float]")).to eql(42.42)
|
||||
expect(e.get("[proxy][array][0]")).to eql("bar")
|
||||
expect(e.get("[proxy][array][1]")).to eql("baz")
|
||||
expect(e.get("[proxy][hash][string]")).to eql("quux")
|
||||
end
|
||||
|
||||
it "should set XXJavaProxy hand crafted" do
|
||||
proxy = com.logstash.Util.getMapFixtureHandcrafted()
|
||||
# proxy is {"string": "foo", "int": 42, "float": 42.42, "array": ["bar","baz"], "hash": {"string":"quux"} }
|
||||
e = LogStash::Event.new()
|
||||
e.set("[proxy]", proxy)
|
||||
expect(e.get("[proxy][string]")).to eql("foo")
|
||||
expect(e.get("[proxy][int]")).to eql(42)
|
||||
expect(e.get("[proxy][float]")).to eql(42.42)
|
||||
expect(e.get("[proxy][array][0]")).to eql("bar")
|
||||
expect(e.get("[proxy][array][1]")).to eql("baz")
|
||||
expect(e.get("[proxy][hash][string]")).to eql("quux")
|
||||
end
|
||||
end
|
||||
|
||||
context "timestamp" do
|
||||
|
|
|
@ -1,44 +1,76 @@
|
|||
package com.logstash;
|
||||
|
||||
import com.logstash.ext.JrubyTimestampExtLibrary;
|
||||
import org.joda.time.DateTime;
|
||||
import org.jruby.RubyArray;
|
||||
import org.jruby.RubyHash;
|
||||
import org.jruby.RubyString;
|
||||
import org.jruby.RubyObject;
|
||||
import org.jruby.RubyBoolean;
|
||||
import org.jruby.RubyArray;
|
||||
import org.jruby.RubyFloat;
|
||||
import org.jruby.RubyInteger;
|
||||
import org.jruby.RubyNil;
|
||||
import org.jruby.RubyBignum;
|
||||
import org.jruby.RubyBoolean;
|
||||
import org.jruby.RubyFixnum;
|
||||
import org.jruby.RubyTime;
|
||||
import org.jruby.RubyFloat;
|
||||
import org.jruby.RubyHash;
|
||||
import org.jruby.RubyInteger;
|
||||
import org.jruby.RubyNil;
|
||||
import org.jruby.RubyString;
|
||||
import org.jruby.RubySymbol;
|
||||
import org.jruby.RubyBignum;
|
||||
import org.jruby.RubyTime;
|
||||
import org.jruby.ext.bigdecimal.RubyBigDecimal;
|
||||
import com.logstash.ext.JrubyTimestampExtLibrary;
|
||||
import org.jruby.java.proxies.JavaProxy;
|
||||
import org.jruby.java.proxies.MapJavaProxy;
|
||||
import org.jruby.javasupport.JavaUtil;
|
||||
import org.jruby.runtime.builtin.IRubyObject;
|
||||
import java.math.BigDecimal;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class Javafier {
|
||||
private static final String ERR_TEMPLATE = "Missing Ruby class handling for full class name=%s, simple name=%s";
|
||||
private static final String PROXY_ERR_TEMPLATE = "Missing Ruby class handling for full class name=%s, simple name=%s, wrapped object=%s";
|
||||
|
||||
private Javafier(){}
|
||||
|
||||
public static List<Object> deep(RubyArray a) {
|
||||
public static List<Object> deep(IRubyObject[] a) {
|
||||
final ArrayList<Object> result = new ArrayList();
|
||||
|
||||
// TODO: (colin) investagate why .toJavaArrayUnsafe() which should be faster by avoiding copying produces nil values spec errors in arrays
|
||||
for (IRubyObject o : a.toJavaArray()) {
|
||||
for (IRubyObject o : a) {
|
||||
result.add(deep(o));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static List<Object> deep(RubyArray a) {
|
||||
return deep(a.toJavaArray());
|
||||
}
|
||||
|
||||
private static HashMap<String, Object> deepMap(final Map<?, ?> map) {
|
||||
final HashMap<String, Object> result = new HashMap();
|
||||
|
||||
for (Map.Entry<?, ?> entry : map.entrySet()) {
|
||||
String k;
|
||||
if (entry.getKey() instanceof IRubyObject) {
|
||||
k = ((IRubyObject) entry.getKey()).asJavaString();
|
||||
} else {
|
||||
k = String.valueOf(entry.getKey());
|
||||
}
|
||||
result.put(k, deepAnything(entry.getValue()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static List<Object> deepList(List<Object> a) {
|
||||
final ArrayList<Object> result = new ArrayList();
|
||||
|
||||
for (Object o : a) {
|
||||
result.add(deepAnything(o));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static HashMap<String, Object> deep(RubyHash h) {
|
||||
final HashMap result = new HashMap();
|
||||
final HashMap<String, Object> result = new HashMap();
|
||||
|
||||
h.visitAll(new RubyHash.Visitor() {
|
||||
@Override
|
||||
|
@ -49,6 +81,20 @@ public class Javafier {
|
|||
return result;
|
||||
}
|
||||
|
||||
private static Object deepAnything(Object o) {
|
||||
// because, although we have a Java object (from a JavaProxy??), it may have IRubyObjects inside
|
||||
if (o instanceof IRubyObject) {
|
||||
return deep((IRubyObject) o);
|
||||
}
|
||||
if (o instanceof Map) {
|
||||
return deepMap((Map) o);
|
||||
}
|
||||
if (o instanceof List) {
|
||||
return deepList((List) o);
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
public static String deep(RubyString s) {
|
||||
return s.asJavaString();
|
||||
}
|
||||
|
@ -101,14 +147,26 @@ public class Javafier {
|
|||
return false;
|
||||
}
|
||||
|
||||
private static Object deepJavaProxy(JavaProxy jp) {
|
||||
Object obj = JavaUtil.unwrapJavaObject(jp);
|
||||
if (obj instanceof IRubyObject[]) {
|
||||
return deep((IRubyObject[])obj);
|
||||
}
|
||||
if (obj instanceof List) {
|
||||
return deepList((List<Object>) obj);
|
||||
}
|
||||
Class cls = jp.getClass();
|
||||
throw new IllegalArgumentException(missingHandlerString(PROXY_ERR_TEMPLATE, cls.getName(), cls.getSimpleName(), obj.getClass().getName()));
|
||||
}
|
||||
|
||||
public static Object deep(IRubyObject o) {
|
||||
// TODO: (colin) this enum strategy is cleaner but I am hoping that is not slower than using a instanceof cascade
|
||||
|
||||
Class cls = o.getClass();
|
||||
RUBYCLASS clazz;
|
||||
try {
|
||||
clazz = RUBYCLASS.valueOf(o.getClass().getSimpleName());
|
||||
clazz = RUBYCLASS.valueOf(cls.getSimpleName());
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new IllegalArgumentException("Missing Ruby class handling for full class name=" + o.getClass().getName() + ", simple name=" + o.getClass().getSimpleName());
|
||||
throw new IllegalArgumentException(missingHandlerString(ERR_TEMPLATE, cls.getName(), cls.getSimpleName()));
|
||||
}
|
||||
|
||||
switch(clazz) {
|
||||
|
@ -127,6 +185,9 @@ public class Javafier {
|
|||
case RubyNil: return deep((RubyNil)o);
|
||||
case True: return deep((RubyBoolean.True)o);
|
||||
case False: return deep((RubyBoolean.False)o);
|
||||
case MapJavaProxy: return deepMap((Map)((MapJavaProxy) o).getObject());
|
||||
case ArrayJavaProxy: return deepJavaProxy((JavaProxy) o);
|
||||
case ConcreteJavaProxy: return deepJavaProxy((JavaProxy) o);
|
||||
}
|
||||
|
||||
if (o.isNil()) {
|
||||
|
@ -134,7 +195,12 @@ public class Javafier {
|
|||
}
|
||||
|
||||
// TODO: (colin) temporary trace to spot any unhandled types
|
||||
System.out.println("***** WARN: UNHANDLED IRubyObject full class name=" + o.getMetaClass().getRealClass().getName() + ", simple name=" + o.getClass().getSimpleName() + " java class=" + o.getJavaClass().toString() + " toString=" + o.toString());
|
||||
System.out.println(String.format(
|
||||
"***** WARN: UNHANDLED IRubyObject full class name=%s, simple name=%s java class=%s toString=%s",
|
||||
o.getMetaClass().getRealClass().getName(),
|
||||
o.getClass().getSimpleName(),
|
||||
o.getJavaClass().toString(),
|
||||
o.toString()));
|
||||
|
||||
return o.toJava(o.getJavaClass());
|
||||
}
|
||||
|
@ -150,12 +216,19 @@ public class Javafier {
|
|||
RubyBoolean,
|
||||
RubyFixnum,
|
||||
RubyBignum,
|
||||
RubyObject,
|
||||
RubyNil,
|
||||
RubyTime,
|
||||
RubySymbol,
|
||||
True,
|
||||
False;
|
||||
False,
|
||||
// these proxies may wrap a java collection of IRubyObject types
|
||||
MapJavaProxy,
|
||||
ArrayJavaProxy,
|
||||
ConcreteJavaProxy
|
||||
}
|
||||
|
||||
private static String missingHandlerString(String fmt, String... subs) {
|
||||
return String.format(fmt, subs);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,13 +1,44 @@
|
|||
package com.logstash;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
public class Util {
|
||||
private Util() {}
|
||||
|
||||
public static Object getMapFixtureJackson() throws IOException {
|
||||
StringBuilder json = new StringBuilder();
|
||||
json.append("{");
|
||||
json.append("\"string\": \"foo\", ");
|
||||
json.append("\"int\": 42, ");
|
||||
json.append("\"float\": 42.42, ");
|
||||
json.append("\"array\": [\"bar\",\"baz\"], ");
|
||||
json.append("\"hash\": {\"string\":\"quux\"} }");
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
return mapper.readValue(json.toString(), Object.class);
|
||||
}
|
||||
|
||||
public static Map<String, Object> getMapFixtureHandcrafted() {
|
||||
HashMap<String, Object> inner = new HashMap<>();
|
||||
inner.put("string", "quux");
|
||||
HashMap<String, Object> map = new HashMap<>();
|
||||
map.put("string", "foo");
|
||||
map.put("int", 42);
|
||||
map.put("float", 42.42);
|
||||
map.put("array", Arrays.asList("bar", "baz"));
|
||||
map.put("hash", inner);
|
||||
return map;
|
||||
}
|
||||
|
||||
public static void mapMerge(Map<String, Object> target, Map<String, Object> add) {
|
||||
for (Map.Entry<String, Object> e : add.entrySet()) {
|
||||
if (target.containsKey(e.getKey())) {
|
||||
|
|
|
@ -2,17 +2,99 @@ package com.logstash;
|
|||
|
||||
import org.jruby.Ruby;
|
||||
import org.jruby.RubyBignum;
|
||||
import java.math.BigInteger;
|
||||
import org.jruby.RubyClass;
|
||||
import org.jruby.RubyMatchData;
|
||||
import org.jruby.RubyString;
|
||||
import org.jruby.java.proxies.ArrayJavaProxy;
|
||||
import org.jruby.java.proxies.ConcreteJavaProxy;
|
||||
import org.jruby.java.proxies.MapJavaProxy;
|
||||
import org.jruby.javasupport.Java;
|
||||
import org.jruby.runtime.builtin.IRubyObject;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import static org.junit.Assert.*;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class JavafierTest {
|
||||
public static final Ruby ruby;
|
||||
static {
|
||||
ruby = Ruby.getGlobalRuntime();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRubyBignum() {
|
||||
RubyBignum v = RubyBignum.newBignum(Ruby.getGlobalRuntime(), "-9223372036854776000");
|
||||
RubyBignum v = RubyBignum.newBignum(ruby, "-9223372036854776000");
|
||||
|
||||
Object result = Javafier.deep(v);
|
||||
assertEquals(BigInteger.class, result.getClass());
|
||||
assertEquals( "-9223372036854776000", result.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMapJavaProxy() {
|
||||
Map<IRubyObject, IRubyObject> map = new HashMap<>();
|
||||
map.put(RubyString.newString(ruby, "foo"), RubyString.newString(ruby, "bar"));
|
||||
RubyClass proxyClass = (RubyClass) Java.getProxyClass(ruby, HashMap.class);
|
||||
MapJavaProxy mjp = new MapJavaProxy(ruby, proxyClass);
|
||||
mjp.setObject(map);
|
||||
|
||||
Object result = Javafier.deep(mjp);
|
||||
assertEquals(HashMap.class, result.getClass());
|
||||
HashMap<String, Object> m = (HashMap) result;
|
||||
assertEquals("bar", m.get("foo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testArrayJavaProxy() {
|
||||
IRubyObject[] array = new IRubyObject[]{RubyString.newString(ruby, "foo")};
|
||||
RubyClass proxyClass = (RubyClass) Java.getProxyClass(ruby, String[].class);
|
||||
ArrayJavaProxy ajp = new ArrayJavaProxy(ruby, proxyClass, array);
|
||||
|
||||
Object result = Javafier.deep(ajp);
|
||||
assertEquals(ArrayList.class, result.getClass());
|
||||
List<Object> a = (ArrayList) result;
|
||||
assertEquals("foo", a.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConcreteJavaProxy() {
|
||||
List<IRubyObject> array = new ArrayList<>();
|
||||
array.add(RubyString.newString(ruby, "foo"));
|
||||
RubyClass proxyClass = (RubyClass) Java.getProxyClass(ruby, ArrayList.class);
|
||||
ConcreteJavaProxy cjp = new ConcreteJavaProxy(ruby, proxyClass, array);
|
||||
Object result = Javafier.deep(cjp);
|
||||
assertEquals(ArrayList.class, result.getClass());
|
||||
List<Object> a = (ArrayList) result;
|
||||
assertEquals("foo", a.get(0));
|
||||
}
|
||||
|
||||
@Rule
|
||||
public ExpectedException exception = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void testUnhandledObject() {
|
||||
RubyMatchData md = new RubyMatchData(ruby);
|
||||
exception.expect(IllegalArgumentException.class);
|
||||
exception.expectMessage("Missing Ruby class handling for full class name=org.jruby.RubyMatchData, simple name=RubyMatchData");
|
||||
Javafier.deep(md);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnhandledProxyObject() {
|
||||
HashSet<Integer> hs = new HashSet<>();
|
||||
hs.add(42);
|
||||
RubyClass proxyClass = (RubyClass) Java.getProxyClass(ruby, HashSet.class);
|
||||
ConcreteJavaProxy cjp = new ConcreteJavaProxy(ruby, proxyClass, hs);
|
||||
exception.expect(IllegalArgumentException.class);
|
||||
exception.expectMessage("Missing Ruby class handling for full class name=org.jruby.java.proxies.ConcreteJavaProxy, simple name=ConcreteJavaProxy, wrapped object=java.util.HashSet");
|
||||
Javafier.deep(cjp);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue