yaml serialization using yamlbeans and fxml properties - java

I'm trying to use YamlBeans to serialize fxml properties. Specificaly a Property. The class has a private property field and the fxml standard getter and setter methods but the information is not saved to the file when serialization occurs.
Entry point:
import java.io.IOException;
public class Main {
public static void main(String[] args) throws IOException{
Person person = new Person(5);
YamlSerializer.serialize(person, System.getProperty("user.dir") + "/person.yml");
}
}
Person.java
import javafx.beans.property.Property;
import javafx.beans.property.SimpleDoubleProperty;
public class Person{
private Property<Number> age;
public Person(){
age = new SimpleDoubleProperty();
age.setValue(3);
}
public Person(Number age){
this.age = new SimpleDoubleProperty(age.doubleValue());
}
public Property<Number> ageProperty() {
return this.age;
}
public Number getAge() {
return this.ageProperty().getValue();
}
public void setAge(final Number age) {
this.ageProperty().setValue(age);
}
}
YamlSerializer.java
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import com.esotericsoftware.yamlbeans.YamlReader;
import com.esotericsoftware.yamlbeans.YamlWriter;
public class YamlSerializer {
public static void serialize(Object object, String path) throws IOException{
File file = new File(path);
if(!file.exists())
file.getParentFile().mkdirs();
YamlWriter writer = new YamlWriter(new FileWriter(path));
writer.write(object);
writer.close();
}
public static Object deserialize(String path) throws IOException{
File file = new File(path);
if(!file.exists()){
if(!file.getParentFile().exists())
if (!file.getParentFile().mkdirs()){
System.out.println("Error creating files");
}
}
YamlReader reader = new YamlReader(new FileReader(path));
return reader.read();
}
}
Output file person.yml:
!Person {}

Your code looks OK. What does Beans getProperties return for your class? This is what YamlWriter uses.
Turns out Beans is finding all the properties, then looking for get/set methods. It looks like it should find your number field and then your setNumber and getNumber methods, but you didn't provide the full class code.
I suggest providing an SSCCE. Trying to help someone without that is a shot in the dark and a time waste.
Edit: The way Beans looks for fields, then a matching setter/getter, Property<Number> age doesn't work. Beans looks for a setter/getter of type Property<Number> and doesn't find them. IIRC it used to use Introspector, but that had to be ripped out because it was missing from Android.
To fix this you would need to patch Beans to be smarter about finding setter/getters without a corresponding field. A PR that does that would be merged.

Related

Java Plugins With A Custom Interface

Like spigot/bukkit plugins id like to be able to load jars in a file and load their classes. Ive managed to get this working with java class loader but the class has to extend a runnable for it to work. Id like to have my own custom interface to implement for each plugin(jar). So I could have functions that get ran on plugin load and so on. If anyone knows how to do this please let me know.
Plugin structure
A plugin is a .jar file. The plugin has a plugin.properties file with the properties of the plugin.
It looks like this:
plugin.main=com.example.plugins.ExamplePlugin
plugin.name=Example Plugin
plugin.description=Test 123
plugin.version=1.0
The file contains the main class, the plugin name, a description and the version.
A plugin must have a class that inherits from the abstract plugin class. This counts as the main class.
Code structure
Let's begin with the Plugin-class:
package com.example.plugins;
public abstract class Plugin {
protected PluginProperty property;
public abstract void onEnable();
public abstract void onDisable();
public PluginProperty getProperty() {
return property;
}
public void setProperty(PluginProperty property) {
this.property = property;
}
}
As you may see, I have chosen an abstract class here.
The class consists of two abstract methods (onEnable and onDisable). The plugin also has a PluginProperty object. The equivalent of this class in Spigot would be JavaPlugin.
Let's take a look at the PluginProperty class.
package com.example.plugins;
public class PluginProperty {
private String main;
private String name;
private String description;
private double version;
public PluginProperty(String main, String name, String description, double version) {
this.main = main;
this.name = name;
this.description = description;
this.version = version;
}
public String getMain() {
return main;
}
public void setMain(String main) {
this.main = main;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public double getVersion() {
return version;
}
public void setVersion(double version) {
this.version = version;
}
}
This class has all the necessary properties of a plugin in it. Most of the things here are self-explanatory, but I would still like to discuss main.
The string holds the name of the main plugin class of the plugin. This is basically the same as the main in the plugin.yml in Spigot.
The loader
Here is the PluginLoader-class:
package com.example.plugins;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Properties;
import java.util.zip.ZipException;
public class PluginLoader {
private static PluginProperty loadPluginProperties(File file) throws ZipException, IOException {
URL url = file.toURI().toURL();
String jarURL = "jar:" + url +"!/plugin.properties";
InputStream input;
URL inputURL = new URL(jarURL);
JarURLConnection conn = (JarURLConnection)inputURL.openConnection();
input = conn.getInputStream();
Properties property = new Properties();
property.load(input);
String main = property.getProperty("plugin.main");
String name = property.getProperty("plugin.name");
String description = property.getProperty("description");
double version = Double.parseDouble(property.getProperty("plugin.version"));
return new PluginProperty(main, name, description, version);
}
public static Plugin loadPlugin(File file) throws IOException, InstantiationException, IllegalAccessException, ClassNotFoundException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
if(!file.exists()) {
return null;
}
PluginProperty property = loadPluginProperties(file);
URL url = file.toURI().toURL();
String jarURL = "jar:" + url + "!/";
URL urls[] = {new URL(jarURL)};
URLClassLoader ucl = new URLClassLoader(urls);
Plugin plugin = (Plugin) Class.forName(property.getMain(), true, ucl).getDeclaredConstructor().newInstance();
plugin.setProperty(property);
return plugin;
}
}
The private loadPluginProperties method loads the plugin properties and returns the required object. The loadPlugin method loads the main class specified in the properties into an object and returns it.
Examples
I just gave you the basic framework for the plugin system. But how should you use it? Let's start with an example loader.
Here is the Main-class:
package com.example.plugins;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
public class Main {
public static List<Plugin> plugins = new ArrayList<Plugin>();
public static void main(String[] args) {
File[] pluginFiles = new File("plugins").listFiles();
//Load plugins
for(File f : pluginFiles) {
if(f.isDirectory()) {
continue;
}
if(!f.getName().endsWith(".jar")) {
continue;
}
Plugin p = null;
try {
p = PluginLoader.loadPlugin(f);
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException | IOException e) {
System.err.println("Failed to load plugin!");
e.printStackTrace();
}
Main.plugins.add(p);
}
//Enable plugins
for(Plugin p : plugins) {
p.onEnable();
}
//Disable plugins
for(Plugin p : plugins) {
p.onDisable();
}
}
}
I won't go into much detail here as I think it's pretty self-explanatory. If you have a question, just ask me through the comments.
After exporting the previously written as a JAR, add it to the classpath in a new project. Don't forget to create a plugin.properties file.
This is an example plugin that is compatible with the .properties file specified above:
package com.example.plugins;
public class ExamplePlugin extends Plugin {
#Override
public void onEnable() {
System.out.println("Hello world!");
}
#Override
public void onDisable() {
}
}
When I export this plugin and put it in the plugins folder I get the following output:
Hello world!
The End
It would be recommended to use JSON or YAML, XML etc. instead of the built-in Java property files. This is the basic structure for plugins. Have fun!

Jackson and generic types inside a subclass

I am rewriting the question since I figured out the actual error in the code.
This is a fully functional example of my issue (I am using Jackson 2.9.0):
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import java.io.IOException;
import java.net.URL;
import java.util.List;
public class MainClass {
public static class SubClass<TYPE> {
private List<TYPE> values;
public List<TYPE> getValues() {
return values;
}
public void setValues(List<TYPE> values) {
this.values = values;
}
}
public static class Foo {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
private SubClass<Foo> propertyFoo;
public SubClass<Foo> getPropertyFoo() {
return propertyFoo;
}
public void setPropertyFoo(SubClass propertyFoo) {
this.propertyFoo = propertyFoo;
}
public static void main(String args[]) throws IOException {
URL url = System.class.getResource("/testFoo.json");
ObjectMapper mapper = new ObjectMapper();
ObjectReader reader = mapper.readerFor(MainClass.class);
MainClass mainClass = reader.readValue(url);
mainClass.getPropertyFoo().getValues().forEach(foo -> {
System.out.println(String.format("name: %s", foo.getName()));
});
}
}
Note the missing type parameter:
public void setPropertyFoo(SubClass propertyFoo)
instead of
public void setPropertyFoo(SubClass<Foo> propertyFoo)
The first form compiles but produces the following exception when run
Exception in thread "main" java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to MainClass$Foo
As I wrote in the newly edited post, my issue was with that setter. Although it compiles it was throwing an exception since Jackson couldn't find the right type to use for the inner objects.
Fixing
public void setPropertyFoo(SubClass propertyFoo)
to
public void setPropertyFoo(SubClass<Foo> propertyFoo)
I know it's a trivial error but since I already spent all this time on finding the issue, maybe it will help somebody. If a mod/admin thinks this is not helpful, I can delete it whole.

Error converting Java interface to Json schema

I am trying to convert a java interface to json schema but it is giving NullPointerException
public interface Contributors {
public List<Contributor> contributors();
public interface Contributor {
public String name();
public String contributorUrl();
public List<String> roles();
}
}
Edit 2:
I am getting the following output:
{"type":"object","$schema":"http://json-schema.org/draft-04/schema#"}
Edit 3:
Following is the code of SchemaGeneratorTest
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.github.reinert.jjschema.exception.TypeException;
import com.github.reinert.jjschema.v1.JsonSchemaFactory;
import com.github.reinert.jjschema.v1.JsonSchemaV4Factory;
public class SchemaGeneratorTest {
private static ObjectMapper mapper = new ObjectMapper();
public static final String JSON_$SCHEMA_DRAFT4_VALUE = "http://json-schema.org/draft-04/schema#";
public static final String JSON_$SCHEMA_ELEMENT = "$schema";
static {
// required for pretty printing
mapper.enable(SerializationFeature.INDENT_OUTPUT);
}
public static void main(String[] args) throws JsonProcessingException, TypeException {
JsonSchemaFactory schemaFactory = new JsonSchemaV4Factory();
schemaFactory.setAutoPutDollarSchema(true);
JsonNode productSchema = schemaFactory.createSchema(Contributors.class);
System.out.println(productSchema);
}
}
The library you are using only reports fields and getters in your schema. Rename your methods to begin with get:
public interface Contributors {
public List<Contributor> getContributors();
}
public interface Contributor {
public String getName();
public String getContributorUrl();
public List<String> getRoles();
}
EDIT: If you can't modify the interfaces, you can use this code to corrupt the "get" string and get it to print all methods anyway. Please don't use it in real production code, as you will cause yourself a lot of trouble.
public class Test {
private static boolean isCorrupted() {
return "haha".startsWith("get");
}
public static void main(String[] args) throws Exception {
String get = "get";
Field value = String.class.getDeclaredField("value");
value.setAccessible(true);
value.set(get, new char[]{});
System.out.println(isCorrupted()); // prints true
}
}

How to get bytecode of cglib proxy class instance?

I'm trying to get bytecode of cglib enhanced object this way using BCEL:
package app;
import cglib.MyInterceptor;
import net.sf.cglib.proxy.Enhancer;
import org.apache.bcel.Repository;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import service.Tool;
public class CgLibApp {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
// target object
Tool tool = new Tool();
// proxying
Enhancer e = new Enhancer();
e.setSuperclass(tool.getClass());
e.setCallback(new MyInterceptor(tool));
Tool proxifiedTool = (Tool) e.create();
// trying to get proxy byte code
JavaClass clazz = Repository.lookupClass(proxifiedTool.getClass());
Method method = clazz.getMethod(Tool.class.getMethod("meth"));
System.out.println(method.getCode().toString());
}
}
But I'm getting:
Exception in thread "main" java.lang.ClassNotFoundException: SyntheticRepository could not load service.Tool$$EnhancerByCGLIB$$22a3afcc
at org.apache.bcel.util.SyntheticRepository.loadClass(SyntheticRepository.java:174)
at org.apache.bcel.util.SyntheticRepository.loadClass(SyntheticRepository.java:158)
at org.apache.bcel.Repository.lookupClass(Repository.java:74)
at app.CgLibApp.main(CgLibApp.java:21)
What should I do to get bytecode from Enhanced object?
BCEL queries a class loader for a .class file in order to get hold of the byte array that represents it. Such a class file does not exist for a dynamically generated class.
In order to get hold of the class file, you have to collect the byte code during the class file's creation. Cglib is built on top of ASM and it allows you to register your own ClassVisitors to collect a class file.
With the Enhancer, use the generateClass(ClassVisitor) method and hand the latter method a ClassWriter. After calling the method, you can get the byte code from the class writer object that you passed.
here is the sample code to print pseudo code of generated CGLIB class.
visitEnd method prints the generated class in text format.
package naga.cglib.demo;
import static org.objectweb.asm.Opcodes.ASM7;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.util.TraceClassVisitor;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.FixedValue;
public class App {
public static void main(String[] args) throws Exception, IllegalArgumentException, InvocationTargetException {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(SampleClass.class);
enhancer.setCallback(new FixedValueImpl());
SampleClass proxy = (SampleClass) enhancer.create();
enhancer.generateClass(new CustomClassWriter());
System.out.println("Hello cglib!" + proxy.test(null));
}
}
class SampleClass {
public String test(String input) {
return "Hello world!";
}
}
class FixedValueImpl implements FixedValue {
#Override
public Object loadObject() throws Exception {
// TODO Auto-generated method stub
return "Hello cglib! from loadObject()";
}
}
class CustomClassWriter extends ClassVisitor {
TraceClassVisitor tracer;
PrintWriter pw = new PrintWriter(System.out);
public CustomClassWriter() {
super(ASM7);
tracer = new TraceClassVisitor(pw);
}
#Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
System.out.println("method name is :" + name);
return tracer.visitMethod(access, name, desc, signature, exceptions);
}
#Override
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
System.out.println("field name is :" + name);
return tracer.visitField(access, name, desc, signature, value);
}
public void visitEnd() {
tracer.visitEnd();
System.out.println(tracer.p.getText());
}
}
I've found this question while researching how to save the CGLIB-generated class in spring-boot 3.0 application (e.g. handling #Transactional or #Configuration-annotated classes). This simple approach may help:
import org.springframework.cglib.core.ReflectUtils;
...
public class SpringCglibUtils {
public static void initGeneratedClassHandler(String targetPath) {
File dir = new File(targetPath);
dir.mkdirs();
ReflectUtils.setGeneratedClassHandler((String className, byte[] classContent) -> {
try (FileOutputStream out = new FileOutputStream(new File(dir, className + ".class"))) {
out.write(classContent);
} catch (IOException e) {
throw new UncheckedIOException("Error while storing " + className, e);
}
});
}
}
and then define in your main class before creating context:
SpringCglibUtils.initGeneratedClassHandler("cglib");
Spring will store to the targetPath directory all generated class files.
Note: unfortunately it's not available before spring-boot 3

Infinite recursion when serializing objects with Jackson and Mockito

I ran into this issue when testing a Spring controller using MockMvc, Mockito and Jackson, so I made a simple class to test out how Jackson behaves. I'm using jackson-databind:2.3.1 and mockito-core:1.9.5.
Given this class:
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.Serializable;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class Person implements Serializable {
private String name;
private int age;
// Public getters and setters...
public static void main(String[] args) {
String name = "Bob";
int age = 21;
ObjectMapper objectMapper = new ObjectMapper();
// attempt serialization with real object
Person person = new Person();
person.setName(name);
person.setAge(age);
try {
System.out.println(objectMapper.writeValueAsString(person));
} catch (JsonProcessingException e) {
e.printStackTrace();
System.err.println("Failed to serialize real object");
}
// attempt serialization with mock object
Person mockPerson = mock(Person.class);
when(mockPerson.getName()).thenReturn(name);
when(mockPerson.getAge()).thenReturn(age);
try {
System.out.println(objectMapper.writeValueAsString(mockPerson));
} catch (JsonProcessingException e) {
e.printStackTrace();
System.err.println("Failed to serialize mock object.");
}
}
Jackson has no problem serializing the real object, however it will throw a JsonMappingException when it tries to serialize the mocked object. Debugging through the code, it's calling serializeFields(bean, jgen, provider) repeatedly, getting stuck on the internal Mockito properties.
So, my question is: Is there anyway to force Jackson to use the getter methods? I tried #JsonIgnoreProperties on the class, #JsonIgnore on the fields, and #JsonProperty on the methods (in different combinations, to no success). Or, do I have to write my own custom serializer?
Thanks!
Here is a solution that will work for you particular case:
First of all you need to create a PersonMixin since you cannot add the required annotations to the mock.
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonProperty;
#JsonAutoDetect(getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE)
public interface PersonMixin {
#JsonProperty
String getName();
#JsonProperty
Integer getAge();
}
Now, use the object mapper like the in following code and you will get the same result as when you serialize the real object:
Person mockPerson = mock(Person.class);
when(mockPerson.getName()).thenReturn(name);
when(mockPerson.getAge()).thenReturn(age);
objectMapper.addMixInAnnotations(Person.class, PersonMixin.class);
try {
System.out.println(objectMapper.writeValueAsString(mockPerson));
} catch (JsonProcessingException e) {
e.printStackTrace();
System.err.println("Failed to serialize mock object.");
}
Here's my ObjectMapper that sorted it out without the need for mixins.
The mapper ignores all members of that has "Mockito" in somewhere in their names.
This solution avoids having a mix-in for each serialized object, or annotating code that may not be accessible.
Running the following test succeeds with the output {"name":"Jonh"}.
package test;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
import org.mockito.Mockito;
public class AppTest extends Mockito {
public void testApp() throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
mapper.setAnnotationIntrospector(new JacksonAnnotationIntrospector() {
#Override
public boolean hasIgnoreMarker(final AnnotatedMember m) {
return super.hasIgnoreMarker(m) || m.getName().contains("Mockito");
}
});
final String name = "Jonh";
Person mockPerson = mock(Person.class);
when(mockPerson.getName()).thenReturn(name);
System.out.println(mapper.writeValueAsString(mockPerson));
}
public static class Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}

Categories