I am using https://mongodb.github.io/mongo-java-driver-reactivestreams/1.11/. It seems to be using https://mongodb.github.io/mongo-java-driver/3.10/. I have a bunch of other registered classes that are working fine. I am using the suggestions at https://mongodb.github.io/mongo-java-driver/3.5/bson/pojos/ (and Save List of interface objects using mongo driver for java) for dealing with fields that have interfaces. However, I get the below error. For other classes for which I get this error, I can simply add an empty constructor to the class, but I cannot do so for an interface. Any help would be appreciated.
Caused by: org.bson.codecs.configuration.CodecConfigurationException: Failed to decode 'SearchCriteria'. Decoding 'filters' errored with: Cannot find a public constructor for 'FilterInterface'.
at org.bson.codecs.pojo.PojoCodecImpl.decodePropertyModel(PojoCodecImpl.java:222)
at org.bson.codecs.pojo.PojoCodecImpl.decodeProperties(PojoCodecImpl.java:197)
at org.bson.codecs.pojo.PojoCodecImpl.decode(PojoCodecImpl.java:121)
at org.bson.codecs.pojo.PojoCodecImpl.decode(PojoCodecImpl.java:125)
at org.bson.codecs.pojo.LazyPojoCodec.decode(LazyPojoCodec.java:57)
at org.bson.codecs.DecoderContext.decodeWithChildContext(DecoderContext.java:93)
at org.bson.codecs.pojo.PojoCodecImpl.decodePropertyModel(PojoCodecImpl.java:213)
... 36 common frames omitted
Caused by: org.bson.codecs.configuration.CodecConfigurationException: Cannot find a public constructor for 'FilterInterface'.
at org.bson.codecs.pojo.CreatorExecutable.checkHasAnExecutable(CreatorExecutable.java:140)
at org.bson.codecs.pojo.CreatorExecutable.getInstance(CreatorExecutable.java:107)
at org.bson.codecs.pojo.InstanceCreatorImpl.<init>(InstanceCreatorImpl.java:40)
at org.bson.codecs.pojo.InstanceCreatorFactoryImpl.create(InstanceCreatorFactoryImpl.java:28)
at org.bson.codecs.pojo.ClassModel.getInstanceCreator(ClassModel.java:71)
at org.bson.codecs.pojo.PojoCodecImpl.decode(PojoCodecImpl.java:120)
at org.bson.codecs.pojo.PojoCodecImpl.decode(PojoCodecImpl.java:125)
at org.bson.codecs.pojo.CollectionPropertyCodecProvider$CollectionCodec.decode(CollectionPropertyCodecProvider.java:74)
at org.bson.codecs.pojo.CollectionPropertyCodecProvider$CollectionCodec.decode(CollectionPropertyCodecProvider.java:43)
at org.bson.codecs.DecoderContext.decodeWithChildContext(DecoderContext.java:93)
at org.bson.codecs.pojo.PojoCodecImpl.decodePropertyModel(PojoCodecImpl.java:213)
... 42 common frames omitted
Below are snippets of my code:
#BsonDiscriminator
public interface FilterInterface<T> {
boolean applyOn(T value);
T getValue();
...
}
public abstract class Filter<T> implements FilterInterface<T> {
public Filter() { }
public abstract boolean applyOn(T value);
public abstract T getValue();
...
}
public class AddressFilter extends Filter<Address> {
public AddressFilter() { }
public boolean applyOn(Address value) {
return true;
}
public Address getValue() {
return new Address();
}
...
}
public class SearchCriteria {
public SearchCriteria() { }
private List<FilterInterface> filters;
}
public static void init() {
String url = <hidden>;
MongoClient mongoClient = MongoClients.create(new ConnectionString(url));
// For POJOs here
// For interface classes.
PojoCodecProvider pojoCodecProvider = PojoCodecProvider.builder()
.conventions(ImmutableList.of(CLASS_AND_PROPERTY_CONVENTION, ANNOTATION_CONVENTION))
.register(SearchCriteria.class)
.register(
ClassModel.builder(FilterInterface.class).enableDiscriminator(true).build(),
ClassModel.builder(Filter.class).enableDiscriminator(true).build(),
ClassModel.builder(AddressFilter.class).enableDiscriminator(true).build())
.automatic(true)
.build();
CodecRegistry codecRegistry = CodecRegistries.fromRegistries(
MongoClientSettings.getDefaultCodecRegistry(),
CodecRegistries.fromProviders(pojoCodecProvider));
String dbName = <hidden>;
mongoDb = mongoClient.getDatabase(dbName).withCodecRegistry(codecRegistry);
}
The example provided in the link works perfectly fine. Much credit goes to that user for this answer.
You have probably inserted the records when FilterInterface was a class or before using the discriminators.
Solution:
Dropping the collection and re-populating will work smoothly.
If it's production scenario, you might wanna add the field _t manually to each document.
Tip: Always use the same code for serialization and deserialization.
Explanation:
Referring to the documentation of the c-sharp driver.
The default discriminator conventions both use an element named _t to store the discriminator value in the BSON document.
If you have inserted the documents before enabling the discriminators, there would be no field _t in the document. When the driver starts decoding, it won't find and fallback to default decoder for the interface FilterInterface.
On the other hand, if you have inserted the documents when FilterInterface was a class, the value of _t will be the fully qualified name of the class. When the decoder starts to decode, it will get the ClassModel and try to create an instance of FilterInterface. Since it is now an interface, the decoder won't find the constructor.
Here is some additional info: you can change the field _t to any other name and you can specify the discriminator value by using over the classes.
#BsonDiscriminator(key = "<field_id>", value = "<value>")
Here is the modified version of the example of that answer. Please run it with discriminators disabled and then run it with discriminators enabled. You will face the same error as yours. Then clean the collection and then try again.
package org.bson.codecs.chng;
import com.google.common.collect.Lists;
import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.Filters;
import org.bson.codecs.configuration.CodecRegistries;
import org.bson.codecs.configuration.CodecRegistry;
import org.bson.codecs.pojo.ClassModel;
import org.bson.codecs.pojo.PojoCodecProvider;
import org.bson.conversions.Bson;
import java.util.Arrays;
import java.util.List;
public class MongoInterfaceTest {
private static MongoClient mongoClient;
static {
init();
}
public static void init() {
try {
ClassModel<User> userClassModel = ClassModel.builder(User.class).enableDiscriminator(false).build();
ClassModel<JavaUser> javaUserClassModel = ClassModel.builder(JavaUser.class).enableDiscriminator(false).build();
ClassModel<PythonUser> pythonUserClassModel = ClassModel.builder(PythonUser.class).enableDiscriminator(false).build();
ClassModel<TestUser> testUserClassModel = ClassModel.builder(TestUser.class).enableDiscriminator(false).build();
CodecRegistry pojoCodecRegistry = CodecRegistries.fromRegistries(
MongoClientSettings.getDefaultCodecRegistry(),
CodecRegistries.fromProviders(
PojoCodecProvider.builder()
.register(
userClassModel,
javaUserClassModel,
pythonUserClassModel,
testUserClassModel
)
.build()
)
);
mongoClient = MongoClients.create(
MongoClientSettings.builder()
.codecRegistry(pojoCodecRegistry)
.applyConnectionString(new ConnectionString(ApplictaionConfig.MONGODB_URL))
.applyToConnectionPoolSettings(builder -> {
builder.minSize(10);
})
.build()
);
} catch (Exception e) {
System.out.println("Connection mongodb failed");
throw new RuntimeException();
}
}
public static void main(String[] args) {
MongoCollection<TestUser> collection = getMongoCollection("TestUser", TestUser.class);
JavaUser javaUser = new JavaUser<Integer>("a");
PythonUser pythonUser = new PythonUser<String>("b", "1");
TestUser testUser = new TestUser(javaUser.name, javaUser);
insertOne(collection, testUser);
testUser = new TestUser(pythonUser.name, pythonUser);
insertOne(collection, testUser);
Bson bson = Filters.and(Filters.eq("name", "a"));
TestUser testUser1 = findFirst(collection, bson);
System.out.println(testUser1);
testUser1.users.forEach(x -> System.out.println(x.dev()));
bson = Filters.and(Filters.eq("name", "b"));
testUser1 = findFirst(collection, bson);
System.out.println(testUser1);
testUser1.users.forEach(x -> System.out.println(x.dev()));
}
/**
* 获得collection对象
*/
public static <T> MongoCollection<T> getMongoCollection(String collectionName, Class<T> tClass) {
MongoDatabase mongoDatabase = mongoClient.getDatabase("kikuu");
MongoCollection<T> collection = mongoDatabase.getCollection(collectionName, tClass);
return collection;
}
public static <T> void insertOne(MongoCollection<T> collection, T document) {
insertMany(collection, Lists.newArrayList(document));
}
public static <T> void insertMany(MongoCollection<T> collection, List<T> documents) {
collection.insertMany(documents);
}
public static <T> T findFirst(MongoCollection<T> collection) {
return (T) collection.find().first();
}
public static <T> T findFirst(MongoCollection<T> collection, Bson bson) {
return (T) collection.find(bson).first();
}
public static interface User<T> {
String dev();
T foo();
}
public static class JavaUser<T> implements User<T> {
public String name;
public JavaUser() {
}
public JavaUser(String name) {
this.name = name;
}
#Override
public String dev() {
return "java";
}
#Override
public String toString() {
return "JavaUser{" +
"name='" + name + '\'' +
'}';
}
#Override
public T foo() {
return null;
}
}
public static class PythonUser<T> implements User<T> {
public String name;
public String age;
public PythonUser() {
}
public PythonUser(String name, String age) {
this.name = name;
this.age = age;
}
#Override
public String dev() {
return "python";
}
#Override
public String toString() {
return "PythonUser{" +
"name='" + name + '\'' +
", age='" + age + '\'' +
'}';
}
#Override
public T foo() {
return null;
}
}
public static class TestUser {
public String name;
public List<User> users;
public TestUser() {
}
public TestUser(String name, User... users) {
this.name = name;
this.users = Arrays.asList(users);
}
#Override
public String toString() {
return "TestUser{" +
"name='" + name + '\'' +
", user=" + users +
'}';
}
}
}
Related
In the Testcontainers documentation, there is an example for having the docker image to be parameterized with #ParameterizedTest.
This was a junit4 example.
https://github.com/testcontainers/testcontainers-java/blob/main/core/src/test/java/org/testcontainers/junit/ParameterizedDockerfileContainerTest.java
#RunWith(Parameterized.class)
public class ParameterizedDockerfileContainerTest {
private final String expectedVersion;
#Rule
public GenericContainer container;
public ParameterizedDockerfileContainerTest(String baseImage, String expectedVersion) {
container =
new GenericContainer(
new ImageFromDockerfile()
.withDockerfileFromBuilder(builder -> {
builder
.from(baseImage)
// Could potentially customise the image here, e.g. adding files, running
// commands, etc.
.build();
})
)
.withCommand("top");
this.expectedVersion = expectedVersion;
}
#Parameterized.Parameters(name = "{0}")
public static Object[][] data() {
return new Object[][] { //
{ "alpine:3.12", "3.12" },
{ "alpine:3.13", "3.13" },
{ "alpine:3.14", "3.14" },
{ "alpine:3.15", "3.15" },
{ "alpine:3.16", "3.16" },
};
}
#Test
public void simpleTest() throws Exception {
final String release = container.execInContainer("cat", "/etc/alpine-release").getStdout();
assertThat(release).as("/etc/alpine-release starts with " + expectedVersion).startsWith(expectedVersion);
}
}
I couldn't find a way to do something similar with junit5, basically :
having the container only started once for all the #ParameterizedTest methods in the class
Ofc, with a lot of if/else, playing with beforeEach, TestInfo, ... is possible but I feel like something is wrong and I'm sure the following question has probably should be answered with junit5
How to use parameterized tests for testing with multiple database versions
So, it seems the equivalent to junit5 is not possible, related to this opened issue https://github.com/junit-team/junit5/issues/878#issuecomment-546459081 as mentioned by Eddù above
With the great help of Michael Simons, I could manage to do something that works
Base class :
#Testcontainers(disabledWithoutDocker = true)
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
public abstract class MultipleNeo4jVersionsTest {
protected static String HEAP_SIZE = "256M";
public static Stream<String> neo4jVersions() {
return Stream.of("4.4.14", "5.2.0");
}
protected static String heapSizeSetting(Neo4jVersion version) {
return version.equals(Neo4jVersion.V4_4)
? "NEO4J_dbms_memory_heap_max__size"
: "NEO4J_server_memory_heap_max__size"
;
}
protected Neo4jContainer<?> getNeo4j(String version) {
var imageName = String.format("neo4j:%s-enterprise", version);
Neo4jVersion neo4jVersion = Neo4jVersion.of(version);
Neo4jContainer<?> container = new Neo4jContainer<>(imageName)
.withoutAuthentication()
.withEnv("NEO4J_ACCEPT_LICENSE_AGREEMENT", "yes")
.withEnv(heapSizeSetting(neo4jVersion), HEAP_SIZE)
.withReuse(true);
container.start();
return container;
}
}
And the actual test class
#ParameterizedTest
#MethodSource("neo4jVersions")
void loading_config(String version) throws Exception {
Neo4jContainer<?> neo4j = getNeo4j(version);
// do something here
}
Couple of useful links
https://foojay.io/today/faster-integration-tests-with-reusable-testcontainers/
https://github.com/michael-simons/neo4j-migrations/blob/main/neo4j-migrations-core/src/test/java/ac/simons/neo4j/migrations/core/CatalogBasedMigrationIT.java
https://github.com/michael-simons/neo4j-migrations/blob/main/neo4j-migrations-core/src/test/java/ac/simons/neo4j/migrations/core/TestBase.java#L49
Devopology Test Engine (Junit5 based) supports parameterized class testing. (I'm the author)
https://github.com/devopology/test-engine
Your example...
package test.neo4j;
import org.devopology.test.engine.api.AfterAll;
import org.devopology.test.engine.api.BeforeAll;
import org.devopology.test.engine.api.Parameter;
import org.devopology.test.engine.api.Test;
import org.testcontainers.containers.Neo4jContainer;
import org.testcontainers.containers.wait.strategy.Wait;
import java.util.ArrayList;
import java.util.Collection;
public class Neo4jTest {
#Parameter
public String dockerImageName;
#Parameter.Supplier
public static Collection<String> dockerImageNames() {
Collection<String> dockerImageNames = new ArrayList<>();
dockerImageNames.add("neo4j:4.4.14-enterprise");
dockerImageNames.add("neo4j:5.2.0-enterprise");
return dockerImageNames;
}
private Neo4jContainer<?> neo4jContainer;
#BeforeAll
public void beforeAll() {
neo4jContainer = new Neo4jContainer<>(dockerImageName)
.waitingFor(Wait.forLogMessage(".*Started..*", 1))
.withEnv("NEO4J_ACCEPT_LICENSE_AGREEMENT", "yes")
.withEnv(heapSizeSetting(dockerImageName), "256M")
.withLogConsumer(outputFrame -> System.out.print(outputFrame.getUtf8String()))
.withoutAuthentication();
neo4jContainer.start();
}
#Test
public void test1() {
System.out.println("test1 : dockerImageName = [" + dockerImageName + "]");
// do something here
}
#Test
public void test2() {
System.out.println("test2 : dockerImageName = [" + dockerImageName + "]");
// do something here
}
#AfterAll
public void afterAll() {
if (neo4jContainer != null) {
try {
neo4jContainer.close();
} catch (Throwable t) {
// DO NOTHING
}
neo4jContainer = null;
}
}
protected static String heapSizeSetting(String dockerImageName) {
return dockerImageName.contains("4.4")
? "NEO4J_dbms_memory_heap_max__size" : "NEO4J_server_memory_heap_max__size";
}
}
I am trying to use my RoomDatabase on the background thread, using RxJava.
My DAO class:
import android.arch.persistence.room.Dao;
import android.arch.persistence.room.Insert;
import java.util.List;
#Dao
public interface MeasurementDAO
{
#Insert
public void insertMeasurements(Measurements m);
}
My Entity class (getter and setter methods left out for brevity):
import android.arch.persistence.room.Entity;
import android.arch.persistence.room.PrimaryKey;
import android.support.annotation.NonNull;
#Entity
public class Measurements
{
#NonNull
#PrimaryKey
public String mId;
public String finalResultIn;
public String finalResultFt;
public String lengthFt;
public String widthFt;
public String heightFt;
public String lengthIn;
public String widthIn;
public String heightIn;
public Measurements(String finalResultFt, String finalResultIn, String lengthFt, String widthFt, String heightFt,
String lengthIn, String widthIn, String heightIn)
{
this.finalResultFt = finalResultFt;
this.finalResultIn = finalResultIn;
this.lengthFt = lengthFt;
this.widthFt = widthFt;
this.heightFt = heightFt;
this.lengthIn = lengthIn;
this.widthIn = widthIn;
this.heightIn = heightIn;
}
}
Finally, here is my MeasurementDatabase class:
#Database(entities = {Measurements.class}, version = 1)
public abstract class MeasurementDatabase extends RoomDatabase
{
private static final String DB_NAME = "measurement_db";
private static MeasurementDatabase instance;
public static synchronized MeasurementDatabase getInstance(Context context)
{
if(instance == null)
{
instance = Room.databaseBuilder(context.getApplicationContext(), MeasurementDatabase.class,
DB_NAME)
.fallbackToDestructiveMigration()
.build();
}
return instance;
}
public abstract MeasurementDAO measurementDAO();
}
In my fragment, I'm trying to insert on the background thread once a menu item is clicked:
final MeasurementDatabase appDb =
MeasurementDatabase.getInstance(getActivity());
//fill the values with the appropriate;
final Measurements m = new Measurements(
cubicInches.getText().toString(),
cubicFeet.getText().toString(),
len_ft.getText().toString(),
width_ft.getText().toString(),
height_ft.getText().toString(),
len_in.getText().toString(),
width_in.getText().toString(),
height_in.getText().toString());
Observable.just(appDb)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<MeasurementDatabase>(){
#Override
public void onSubscribe(Disposable d) {
appDb.measurementDAO().insertMeasurements(m);
}
#Override
public void onNext(MeasurementDatabase measurementDatabase)
{
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
}
});
I am getting an error saying:
java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.
What is missing from my RxJava code thats not putting the process on the background thread?
Create an Observable and write your logic inside it. You can subscribe the observable and get the boolean.
public Observable<Boolean> insertObject(Measurements m) {
return Observable.create(new ObservableOnSubscribe<Boolean>() {
#Override
public void subscribe(ObservableEmitter<Boolean> e) {
appDb.measurementDAO().insertMeasurements(m);
e.onNext(true);
e.onComplete();
}
}).subscribeOn(Schedulers.io());
}
Thanks to some clues from #CommonsWare, I was able to find the piece that I was missing:
Completable.fromAction(() -> appDb.measurementDAO().insertMeasurements(m))
.subscribeOn(Schedulers.io())
.subscribe();
I know you have an answer, however you never know if this fails. I'm sure your dao insert method could return a long[] (id's of inserted rows).
You could easily do:
Completable.fromCallable(() ->
appDb.measurementDAO().insertMeasurements(m).length != 0 ?
Completable.complete() :
Completable.error(new IllegalStateException("Error inserting " + m.toString())))
.subscribeOn(Schedulers.io())
.subscribe(() -> { }, Throwable::printStackTrace);
I have a Java Backend responding rest request with response with this class:
import java.util.Collection;
import java.util.Collections;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import org.codehaus.jackson.map.annotate.JsonSerialize;
#XmlRootElement
#JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
public class RestResponse<T> {
#XmlElement(name = "METADATA")
private JsonMetadata jsonMetadata;
private Collection<T> result;
public RestResponse() {
jsonMetadata = new JsonMetadata();
}
public RestResponse(JsonMetadata metadata) {
this.jsonMetadata = metadata;
}
public JsonMetadata getJsonMetadata() {
return jsonMetadata;
}
public void setJsonMetadata(JsonMetadata jsonMetadata) {
this.jsonMetadata = jsonMetadata;
}
public Collection<T> getResult() {
return result;
}
public void setResult(Collection<T> result) {
this.result = result;
}
public void setObjectList(Collection<T> objectList) {
if (objectList != null) {
this.result = objectList;
}
}
public void setObject(T object) {
if (object != null) {
setObjectList(Collections.singletonList(object));
}
}
public void setErrorMessage(String msg) {
jsonMetadata.setErrorMessage(msg);
}
public void setWarnMessage(String msg) {
jsonMetadata.setWarnMessage(msg);
}
}
And works ok sending something like this:
METADATA: {STATUS: "0", ERROR_MESSAGE: ""}
result: [{id: "4010", name: "Demo"}]
Now I'm trying to use Apache Syncope and want to use maven artifact like read hear:
https://syncope.apache.org/docs/reference-guide.html#client-library
but when I add this lines:
<dependency>
<groupId>org.apache.syncope.client</groupId>
<artifactId>syncope-client-lib</artifactId>
<version>2.1.2</version>
</dependency>
To the pom.xml in my proyect in Eclipse. Only add this lines, Do not do anything else, and then the rest response changes to:
jsonMetadata: {status: "0", errorMessage: ""}
result: [{id: "4010", name: "Demo"}]
For me is a problem because I manage the errors whit this 'METADATA' word.
Does anyone know why this change occurs?
In this case you define #XmlElement(name = "METADATA") only in for the first one field JsonMetadata. Remember Java only get this annotation to the first field under it!
When i create xml i prefer to use the notation in their get method, for example:
#XmlRootElement(name = "root")
public class RestResponse<T> {
#XmlElement(name = "metadata")
public JsonMetadata getJsonMetadata() {
return jsonMetadata;
}
public void setJsonMetadata(JsonMetadata jsonMetadata) {
this.jsonMetadata = jsonMetadata;
}
public void setResult(<any> result) {
this.result = result;
}
#XmlElement(name="result")
public <any> getResult() {
return result;
}
REMEMBER: you have to create both setter and getter for each field! with the correct name (I use netbeans ide and it suggest automatically to add this method with the correct name).
BUT there is another solution...
#XmlAccessorType(XmlAccessType.FIELD)
public class RestResponse<T> {
#XmlElement(name = "METADATA")
private JsonMetadata jsonMetadata;
private Collection<T> result;
//...
with this notation before the class you shuld risolve your problem.
So there are 2 way:
-add the method (i prefer this one)
-add this notatio (witout add or touch anything, i don't like because the method are more usefull)
it is not possible to use the two solutions together!
I have an instance of class Address, which I have to change according to environment:
1) Region: base class with sub-classes RegionA and RegionB
2) Site: base class with sub-classes SiteA, SiteB and SiteC
3) Language: base class with sub-classes LanguageA and LanguageB
Each subclass defines constraints about Address modification.
The problem is that each tuple (Region, Site, Language) has to define its own modifier.
So, I have a method adjust(Address a, Region r, Site s, Language l):
void adjust(Address a, Region r, Site s, Language l){
if(r instanceof Russia && s instanceof MailRu && Language instanceof Russian){
a.set_street("abc")
}
else if(r instanceof Russia && s instanceof MailRu && Language instanceof English){
a.set_street("fgh")
}
}
What is the best design patter to use in this case?
Use polymorphism to loose the ifs and instanceofs!
Use the abstract factory pattern for easier creation of the street info.
Region and Language are the (sub)products (resp. their factories, when you consider the way I did it), which are used to create the street in Address.
package address.example;
public class AddressExample
{
public static void main(String[] args)
{
LanguageFactoryProvider lfp = new LanguageFactoryProvider.LanguageFactoryProviderImpl();
RegionFactoryProvider rfp = new RegionFactoryProvider.RegionFactoryProviderImpl();
AddressProvider provider = new AddressProvider(lfp, rfp);
Address a = provider.createAddress("RU", "USA", "Famous Street");
System.out.println(a.getStreet());
System.out.println("-----");
Address b = provider.createAddress("EN", "RUS", "Good Street");
System.out.println(b.getStreet());
}
}
Output is
Address format: RU
Famous Street
USA
-----
Address format: EN
Good Street
RUS
This is the Address class, as you can see it delegates parts of the street creation to region and language (it's nothing fancy, but you get the point).
package address.example;
import address.example.LanguageFactoryProvider.Language;
import address.example.RegionFactoryProvider.Region;
public interface Address
{
public String getStreet();
static class AddressImpl implements Address
{
private final Region region;
private final Language language;
private final String street;
public AddressImpl(Region region, Language language, String street)
{
this.region = region;
this.language = language;
this.street = street;
}
#Override
public String getStreet()
{
StringBuilder sb = new StringBuilder();
sb.append(String.format("Address format: %s", language.getSpecifier()));
sb.append(String.format("%n"));
sb.append(street);
sb.append(String.format("%n"));
sb.append(region.getSpecifier());
return sb.toString();
}
}
}
And here are the other used classes. I'll add some more thoughts to it another time.
package address.example;
import address.example.LanguageFactoryProvider.Language;
import address.example.RegionFactoryProvider.Region;
public class AddressProvider
{
private final LanguageFactoryProvider lfp;
private final RegionFactoryProvider rfp;
public AddressProvider(LanguageFactoryProvider lfp, RegionFactoryProvider rfp)
{
this.lfp = lfp;
this.rfp = rfp;
}
public Address createAddress(String language, String region, String street)
{
Language _language = lfp.getLanguageFactory(language).createLanguage();
Region _region = rfp.getRegionFactory(region).createRegion();
return new Address.AddressImpl(_region, _language, street);
}
}
package address.example;
import java.util.HashMap;
import java.util.Map;
public interface LanguageFactoryProvider
{
public LanguageFactory getLanguageFactory(String language);
static interface LanguageFactory
{
public Language createLanguage();
}
static interface Language
{
public String getSpecifier();
}
static class LanguageImpl implements Language
{
private final String specifier;
public LanguageImpl(String specifier)
{
this.specifier = specifier;
}
#Override
public String getSpecifier()
{
return specifier;
}
}
static class LanguageFactoryProviderImpl implements LanguageFactoryProvider
{
private static final Map<String, LanguageFactory> factories = new HashMap<>();
static
{
factories.put("EN", new EnglishLanguageFactory());
factories.put("RU", new RussianLanguageFactory());
}
#Override
public LanguageFactory getLanguageFactory(String language)
{
if (!factories.containsKey(language))
throw new IllegalArgumentException();
LanguageFactory factory = factories.get(language);
return factory;
}
}
static class RussianLanguageFactory implements LanguageFactory
{
#Override
public Language createLanguage()
{
return new LanguageImpl("RU");
}
}
static class EnglishLanguageFactory implements LanguageFactory
{
#Override
public Language createLanguage()
{
return new LanguageImpl("EN");
}
}
}
package address.example;
import java.util.HashMap;
import java.util.Map;
public interface RegionFactoryProvider
{
public RegionFactory getRegionFactory(String region);
static interface RegionFactory
{
public Region createRegion();
}
static interface Region
{
public String getSpecifier();
}
static class RegionImpl implements Region
{
private final String specifier;
public RegionImpl(String specifier)
{
this.specifier = specifier;
}
#Override
public String getSpecifier()
{
return specifier;
}
}
static class RegionFactoryProviderImpl implements RegionFactoryProvider
{
private static final Map<String, RegionFactory> factories = new HashMap<>();
static
{
factories.put("RUS", new RussianRegionFactory());
factories.put("USA", new UsRegionFactory());
}
#Override
public RegionFactory getRegionFactory(String region)
{
if (!factories.containsKey(region))
throw new IllegalArgumentException();
RegionFactory factory = factories.get(region);
return factory;
}
}
static class RussianRegionFactory implements RegionFactory
{
#Override
public Region createRegion()
{
return new RegionImpl("RUS");
}
}
static class UsRegionFactory implements RegionFactory
{
#Override
public Region createRegion()
{
return new RegionImpl("USA");
}
}
}
This is typical business "logic" with many cases/rules. It pays to make a declarative solution for this.
<rule>
<when category="Region" value="Russia"/>
<when category="Site" value="MailRu"/>
<action category="Address" value="abc"/>
</rule>
This allows to build in diagnostics, integrity checks, log uncovered cases, make historical logs for future analysis on future bug reports.
It might even be more readable. Transformable in a nice HTML table hierarchy for manager level documentation.
It boils down to the fact that your code is procedural, without possibility to store the control-flow path taken. A model-driven approach can alleviate that. A DSL would be feasible, but I find a free form data approach to be a bit more creative, direct.
I want to achieve the following xml using simple xml framework (http://simple.sourceforge.net/):
<events>
<course-added date="01/01/2010">
...
</course-added>
<course-removed date="01/02/2010">
....
</course-removed>
<student-enrolled date="01/02/2010">
...
</student-enrolled>
</events>
I have the following (but it doesn't achieve the desired xml):
#Root(name="events")
class XMLEvents {
#ElementList(inline=true)
ArrayList<XMLEvent> events = Lists.newArrayList();
...
}
abstract class XMLEvent {
#Attribute(name="date")
String dateOfEventFormatted;
...
}
And different type of XMLNodes that have different information (but are all different types of events)
#Root(name="course-added")
class XMLCourseAdded extends XMLEvent{
#Element(name="course")
XMLCourseLongFormat course;
....
}
#Root(name="course-removed")
class XMLCourseRemoved extends XMLEvent {
#Element(name="course-id")
String courseId;
...
}
How should I do the mapping or what should I change in order to be able to achieve de desired xml?
Thanks!
A very clean way to solve the problem is to create your own converter such as:
public class XMLEventsConverter implements Converter<XMLEvents> {
private Serializer serializer;
XMLEventsConverter(Serializer serializer){
this.serializer = serializer;
}
#Override
public XMLEvents read(InputNode arg0) throws Exception {
return null;
}
#Override
public void write(OutputNode node, XMLEvents xmlEvents) throws Exception {
for (XMLEvent event : xmlEvents.events) {
serializer.write(event, node);
}
}
}
And then use a RegistryStrategy and bind the class XMLEvents with the previous converter:
private final Registry registry = new Registry();
private final Serializer serializer = new Persister(new RegistryStrategy(registry));
....
registry.bind(XMLEvents.class, new XMLEventsConverter(serializer));
In this way the xml obtained is the one desired. Note that the read method on the XMLEventsConverter just return null, in case you need to rebuild the object from the xml file you should properly implement it.
Regards!
Looks like you can do this now using the #ElementListUnion functionality
import org.simpleframework.xml.Attribute;
import org.simpleframework.xml.ElementList;
import org.simpleframework.xml.ElementListUnion;
import org.simpleframework.xml.core.Persister;
import java.io.StringReader;
import java.util.List;
/**
* Created by dan on 3/09/16.
*/
public class XMLEventsTest {
public static final String XML_EVENTS = "<?xml version=\"1.0\" " +
"encoding=\"ISO-8859-1\"?><events>" +
"<course-added date=\"2016/10/1\"/>" +
"<course-removed date=\"2016/10/2\"/>" +
"</events>";
public static void main(String args[]) throws Exception {
Persister persister = new Persister();
XMLEvents events = persister.read(XMLEvents.class, new StringReader(XML_EVENTS));
for (XMLEvent e : events.events) {
System.out.println("found event: " + e);
}
}
public static class XMLEvents {
#ElementListUnion({
#ElementList(entry = "course-added", type = XMLCourseAdded.class, inline = true),
#ElementList(entry = "course-removed", type = XMLCourseRemoved.class, inline = true),
})
private List<XMLEvent> events;
}
public static class XMLEvent {
#Attribute(name = "date")
private String dateOfEventFormatted;
public String getDateOfEventFormatted() {
return dateOfEventFormatted;
}
#Override
public String toString() {
return getClass().getSimpleName() + "[" + dateOfEventFormatted + "]";
}
}
public static class XMLCourseAdded extends XMLEvent {
}
public static class XMLCourseRemoved extends XMLEvent {
}
}
This will print out:
found event: XMLCourseAdded[2016/10/1]
found event: XMLCourseRemoved[2016/10/2]