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:
Guy Boertje 2016-06-17 20:26:17 +01:00 committed by Suyog Rao
parent f62463421d
commit c937160364
4 changed files with 240 additions and 27 deletions

View file

@ -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

View file

@ -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);
}
}

View file

@ -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())) {

View file

@ -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);
}
}