Spring Reactive Web Flux/REST API, filtering data - spring-webflux

I am trying to stream data from an API (using a sample API from sampleapis.com) using a given JSON API. Bear with me, I'm new to this.
I have successfully modeled the data from the countries API (found here: https://sampleapis.com/api-list/countries), and I have managed to create a simple script to stream this unfiltered data. I want to be able to do a form on the index page which allows me to specify some criteria by which to filter/search this data. For example, ordering countries by population, or inputting a value x into a form and returns the countries/country data for whichever countries exceed that input number.
Here is my REST Controller currently (taking no inputs, just streaming the raw API data):
(Can I create other Controllers for other functions I perform on the stream? Or can the main REST Controller handle what I need if I just add some criteria to it??)
#RestController
public class Controller {
#GetMapping(path = "/countries", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
Flux<Countries> countries(){
return WebClient.create("https://api.sampleapis.com/countries/countries")
.get()
.retrieve()
.bodyToFlux(Countries.class)
.delayElements(Duration.ofSeconds(2));
}
}
And here is my View Controller
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
#Controller
public class ViewController {
#GetMapping("/")
String index(){
return "index";
}
}
Here is what I'm doing on the index.HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>abbreviation</h1>
<h1 id = "abbreviation"></h1>
<h1>capital</h1>
<h1 id = "capital"></h1>
<h1>currency</h1>
<h1 id = "currency"></h1>
<h1>name</h1>
<h1 id = "name"></h1>
<h1>phone</h1>
<h1 id = "phone"></h1>
<h1>population</h1>
<h1 id = "population"></h1>
<h1>mediaflag</h1>
<h1 id = "mediaflag"></h1>
<h1>mediaemblem</h1>
<h1 id = "mediaemblem"></h1>
<h1>mediaorthographic</h1>
<h1 id = "mediaorthographic"></h1>
<script>
var source = new EventSource("http://localhost:8080/countries");
source.onopen = function(){
console.log("connection opened")
}
source.onerror = function(error){
console.log("error occurred", error);
};
source.onmessage = function(event){
var number = event.data;
var h1 = document.getElementById("number");
h1.innerHTML = number;
};
source.onmessage = function(event){
var parsedJSON = JSON.parse(event.data);
document.getElementById("abbreviation").innerHTML = parsedJSON.abbreviation;
document.getElementById("currency").innerHTML = parsedJSON.currency;
document.getElementById("capital").innerHTML = parsedJSON.capital;
document.getElementById("name").innerHTML = parsedJSON.name;
document.getElementById("phone").innerHTML = parsedJSON.phone;
document.getElementById("population").innerHTML = parsedJSON.population;
document.getElementById("mediaflag").innerHTML = parsedJSON.media.flag;
document.getElementById("mediaemblem").innerHTML = parsedJSON.media.emblem;
document.getElementById("mediaorthographic").innerHTML = parsedJSON.media.orthographic;
}
</script>
</body>
</html>
And here is how I've modeled the data
public class Countries {
private String abbreviation;
private String capital;
private String currency;
private String name;
private String phone;
private int population;
private Media media;
public Countries(){
}
public String getAbbreviation() {
return abbreviation;
}
public void setAbbreviation(String abbreviation) {
this.abbreviation = abbreviation;
}
public String getCapital() {
return capital;
}
public void setCapital(String capital) {
this.capital = capital;
}
public String getCurrency() {
return currency;
}
public void setCurrency(String currency) {
this.currency = currency;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public int getPopulation() {
return population;
}
public void setPopulation(int population) {
this.population = population;
}
public Media getMedia() {
return media;
}
public void setMedia(Media media) {
this.media = media;
}
}
l
public class Media {
private String flag;
private String emblem;
private String orthographic;
public Media(){
}
public String getFlag() {
return flag;
}
public void setFlag(String flag) {
this.flag = flag;
}
public String getEmblem() {
return emblem;
}
public void setEmblem(String emblem) {
this.emblem = emblem;
}
public String getOrthographic() {
return orthographic;
}
public void setOrthographic(String orthographic) {
this.orthographic = orthographic;
}
}
As an aside, I'm not sure if this is correct, but I created another controller which takes an input int value and returns the countries whose populations exceed this value. If I put a value /populations/intVALUE into my web browser it successfully returns those objects - I'm just not sure how to access this data in normal HTML! Am I doing it right here? Regardless, my main question is regarding filtering the stream data based on my inputs.
Here is that code snippet:
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.function.client.WebClient;
import com.example.assignmentreactivesystemsv2.models.Countries;
import reactor.core.publisher.Flux;
#RestController
public class Test {
#GetMapping(path = "/population/{x}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
Flux<Countries> countries(#PathVariable int x){
return WebClient.create("https://api.sampleapis.com/countries/countries")
.get()
.retrieve()
.bodyToFlux(Countries.class)
.filter(s -> (s.getPopulation()>x));
}
}
Thanks very much in advance.

Related

Value Dependent Deserialization with Jackson

I want to deserialize into a data structure. Dependent on the version of the JSON data I want to deserialize into different implementations of the same interface. And this works so far with a custom deserializer.
However, in the data structure I use references. And I expect that when undefined references are encountered an exception is thrown. The way I programmed it, this does not work together with the interface.
I created a small example with a (currently not passing) test case to show the desired behavior.
Additional Information:
In the test case, when I use concrete classes (instead of the interface) in readValue the desired behavior occurs. That is, when I write mapper.readValue(buggy, Database2.class); instead of mapper.readValue(buggy, DatabaseI.class);. But then I lose the ability to abstract from the particular content of the JSON data.
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.btc.adt.pop.scen.objectstreams.Person;
import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.node.IntNode;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.Test;
public class Example {
#Test
public void test() throws JsonProcessingException {
ObjectMapper mapper =
new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
SimpleModule module = new SimpleModule();
module.addDeserializer(DatabaseI.class, new ToyDeserializer());
mapper.registerModule(module);
String correct = "{'version':1,'people':[{'id':'a','friends':['b','c']},{'id':'b','friends':['c']},{'id':'c','friends':['b']}]}";
DatabaseI deserCorrect = mapper.readValue(correct, DatabaseI.class);
System.out.println(mapper.writeValueAsString(deserCorrect));
String buggy = "{'version':2,'people':[{'id':'a','friends':['b','c']},{'id':'b','friends':['c']},{'id':'c','friends':['FOO']}]}";
assertThrows(Exception.class, () -> {
mapper.readValue(buggy, DatabaseI.class);
}, "The reference FOO is undefined. An Exception should be thrown.");
}
}
class Person {
#JsonProperty("id")
private String id;
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
#JsonIdentityReference(alwaysAsId = true)
private List<Person> friends = new ArrayList<>();
public Person() {
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public List<Person> getFriends() {
return friends;
}
public void setFriends(List<Person> friends) {
this.friends = friends;
}
}
interface DatabaseI {
}
class Database1 implements DatabaseI {
private int version;
private List<Person> people = new ArrayList<>();
public Database1() {
}
public List<Person> getPeople() {
return people;
}
public void setPeople(List<Person> people) {
this.people = people;
}
public int getVersion() {
return version;
}
public void setVersion(int version) {
this.version = version;
}
}
class Database2 implements DatabaseI {
private String version;
private List<Person> people = new ArrayList<>();
public Database2() {
}
public List<Person> getPeople() {
return people;
}
public void setPeople(List<Person> people) {
this.people = people;
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
}
class ToyDeserializer extends StdDeserializer<DatabaseI> {
protected ToyDeserializer(Class<?> vc) {
super(vc);
}
public ToyDeserializer() {
this(null);
}
#Override
public DatabaseI deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JacksonException {
ObjectMapper mapper = (ObjectMapper) jp.getCodec();
JsonNode node = mapper.readTree(jp);
int version = (Integer) ((IntNode) node.get("version")).numberValue();
if (version == 1) {
return mapper.treeToValue(node, Database1.class);
} else {
return mapper.treeToValue(node, Database2.class);
}
}
}
This very good question! If you want to understand why no exception is thrown, your class Person must look like this:
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id",
scope = Person.class,
resolver = SimpleObjectIdResolverThrowsException.class
)
#JsonIdentityReference
class Person {
String id;
List<Person> friends = new ArrayList<>();
#ConstructorProperties({"id"})
public Person(String id) {
this.id = id;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public List<Person> getFriends() {
return friends;
}
public void setFriends(List<Person> friends) {
this.friends = friends;
}
}
class SimpleObjectIdResolverThrowsException extends SimpleObjectIdResolver {
public SimpleObjectIdResolverThrowsException() {
super();
}
#Override
public Object resolveId(ObjectIdGenerator.IdKey id) {
if (this._items == null) {
return null;
}
Object obj = this._items.get(id);
if (obj == null) {
throw new RuntimeException("Unresolved reference for: " + id);
}
return obj;
}
#Override
public ObjectIdResolver newForDeserialization(Object context) {
return new SimpleObjectIdResolverThrowsException();
}
}
Now you can set break point in the method resolveId and see what happens when we de-serialize the string "{'version':1,'people':[{'id':'a','friends':['b','c']},{'id':'b','friends':['c']},{'id':'c','friends':['b']}]}":
The problem is that the objects are processed one after the other and the references from the friends list are not resolved at that time.

How to assert/validate the JSON body and properties returned by a Micronaut controller

I am a Micronaut/Java beginner and I am trying to design some tests for my controllers. I could not find many examples online so here is my question.
Below is the controller with 2 #GET requests:
#Controller("/api/v1")
public class MyController {
private final ClientNetworkList clientNetworkList;
private final ClientStatus clientStatus;
public MyController(
ClientNetworkList clientNetworkList,
ClientStatus clientStatus
){
this.ClientNetworkList = clientNetworkList;
this.ClientStatus = clientStatus;
}
#Get(uri = "/networkList", produces = MediaType.APPLICATION_JSON_STREAM)
Flowable<NetworkListPackage> packagesNetworkList() {
return ClientNetworkList.fetchPackages();
}
#Get(uri = "/channels/{stringParm}/status/", produces = MediaType.APPLICATION_JSON_STREAM)
Flowable<ChannelStatusPackage> packagesStatus(stringParm) {
return ClientStatus.fetchPackages(genesis);
}
}
The java object POJOs:
#Introspected
public class NetworkListPackage {
private List<NetworkList> networkList = null;
#JsonIgnore
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
public List<NetworkList> getNetworkList() {
return networkList;
}
public void setNetworkList(List<NetworkList> networkList) {
this.networkList = networkList;
}
public Map<String, Object> getAdditionalProperties() {
return this.additionalProperties;
}
public void setAdditionalProperty(String name, Object value) {
this.additionalProperties.put(name, value);
}
}
public class NetworkList {
private String name;
private Boolean authEnabled;
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Boolean getAuthEnabled() {
return authEnabled;
}
public void setAuthEnabled(Boolean authEnabled) {
this.authEnabled = authEnabled;
}
public Map<String, Object> getAdditionalProperties() {
return this.additionalProperties;
}
public void setAdditionalProperty(String name, Object value) {
this.additionalProperties.put(name, value);
}
}
#Introspected
public class ChannelStatusPackage {
private String chaincodeCount;
private String txCount;
private String latestBlock;
private String peerCount;
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
public String getChaincodeCount() {
return chaincodeCount;
}
public void setChaincodeCount(String chaincodeCount) {
this.chaincodeCount = chaincodeCount;
}
public String getTxCount() {
return txCount;
}
public void setTxCount(String txCount) {
this.txCount = txCount;
}
public String getLatestBlock() {
return latestBlock;
}
public void setLatestBlock(String latestBlock) {
this.latestBlock = latestBlock;
}
public String getPeerCount() {
return peerCount;
}
public void setPeerCount(String peerCount) {
this.peerCount = peerCount;
}
public Map<String, Object> getAdditionalProperties() {
return this.additionalProperties;
}
public void setAdditionalProperty(String name, Object value) {
this.additionalProperties.put(name, value);
}
}
And the potential tests:
#MicronautTest
class MyControllerTest {
#Inject
#Client("/")
RxStreamingHttpClient client;
#Test
public void verifyChannelStatusPackagesCanBeFetchedWithCompileTimeAutoGeneratedAtClient() {
//when:
HttpRequest request = HttpRequest.GET("/api/v1/channels/{stringParam}/status/");
Flowable<ChannelStatusPackage> channelStatusPackageStream = client.jsonStream(request, ChannelStatusPackage.class);
Iterable<ChannelStatusPackage> channelStatusPackages = channelStatusPackageStream.blockingIterable();
//then:
//How to assert the returned body compared to the POJO?
//How to handle the parameter in the request url?
#Test
public void verifyNetworkListPackagesCanBeFetchedWithCompileTimeAutoGeneratedAtClient() {
//when:
HttpRequest request = HttpRequest.GET("/api/v1/networkList");
Flowable<NetworkListPackage> networkListPackageStream = client.jsonStream(request, NetworkListPackage.class);
Iterable<NetworkListPackage> networkListPackages = networkListPackageStream.blockingIterable();
//then:
//How to assert the returned body and compared to the POJO?
//How to assert the returned properties ?
}
}
Based on the previous code, how can I test that the returned bodies and properties of the requests matches the POJOs?
What are the usual test to be carried out?
Thank you very much for helping.
Normaly, the basic assertion start by testing the object type, so this should validate your schema.
An other way to test it is to use RestAssured, witch is a bit more readable.
You need to import the fallowing dependencies in you build.gradle
testImplementation("io.rest-assured:rest-assured:4.2.+")
testImplementation("io.rest-assured:json-schema-validator:4.2.+")
You need test annotation processor to enable micronaut injection and junit 5 for the BeforeEach.
The full test dependencies:
testAnnotationProcessor("io.micronaut:micronaut-inject-java")
testImplementation("org.junit.jupiter:junit-jupiter-api")
testImplementation("io.micronaut.test:micronaut-test-junit5")
testImplementation("io.rest-assured:rest-assured:4.2.+")
testImplementation("io.rest-assured:json-schema-validator:4.2.+")
testRuntime("org.junit.jupiter:junit-jupiter-engine")
Then you can wright your tests like that:
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.equalTo;
import io.micronaut.http.HttpStatus;
import io.micronaut.runtime.server.EmbeddedServer;
import io.micronaut.test.annotation.MicronautTest;
import io.restassured.RestAssured;
import javax.inject.Inject;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
#MicronautTest
class MyControllerTest {
#Inject
private EmbeddedServer embeddedServer;
#BeforeEach
public void setUp() {
RestAssured.port = embeddedServer.getPort();
}
#Test
public void verifyChannelStatusPackagesCanBeFetchedWithCompileTimeAutoGeneratedAtClient() {
given()
.when()
.pathParam("stringParam", "value")
.get("/api/v1/channels/{stringParam}/status/")
.then()
.statusCode(HttpStatus.OK.getCode())
.body(
"chaincodeCount", equalTo("chaincodeCountValue"),
"txCount", equalTo("txCountValue"),
"latestBlock", equalTo("latestBlockValue"),
"peerCount", equalTo("peerCountValue"),
"additionalProperties.key1", equalTo("additionalPropertyValue1"),
"additionalProperties.key2", equalTo("additionalPropertyValue2")
);
}
#Test
public void verifyNetworkListPackagesCanBeFetchedWithCompileTimeAutoGeneratedAtClient() {
given()
.when()
.get("/api/v1/networkList")
.then()
.statusCode(HttpStatus.OK.getCode())
.body(
"networkList.name[0]", equalTo("nameValue0"),
"networkList.authEnabled[0]", equalTo("authEnabledValue0"),
"networkList.additionalProperties[0].key1", equalTo("additionalPropertiesValue1"),
"networkList.additionalProperties[0].key2", equalTo("additionalPropertyValue2")
);
}
}
This is not really the way you wanted to do your tests, but I hope it will help.
So I ended up using the "hasItems" matcher or/and the jackson schema matcher.
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.equalTo;
import io.micronaut.http.HttpStatus;
import io.micronaut.runtime.server.EmbeddedServer;
import io.micronaut.test.annotation.MicronautTest;
import io.restassured.RestAssured;
import javax.inject.Inject;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.hamcrest.Matchers.hasItems;
import static io.restassured.module.jsv.JsonSchemaValidator.matchesJsonSchemaInClasspath;
#MicronautTest
class MyControllerTest {
#Inject
private EmbeddedServer embeddedServer;
#BeforeEach
public void setUp() {
RestAssured.port = embeddedServer.getPort();
}
#Test
public void verifyChannelStatusPackagesCanBeFetchedWithCompileTimeAutoGeneratedAtClient() {
given()
.when()
.pathParam("stringParam", "value")
.get("/api/v1/channels/{stringParam}/status/")
.then()
.statusCode(HttpStatus.OK.getCode())
.body(matchesJsonSchemaInClasspath("channelsStatus.json"))
.body("keySet()",hasItems(
"chaincodeCount",
"txCount",
"latestBlock",
"peerCount",
);
}
#Test
public void verifyNetworkListPackagesCanBeFetchedWithCompileTimeAutoGeneratedAtClient() {
given()
.when()
.get("/api/v1/networkList")
.then()
.statusCode(HttpStatus.OK.getCode())
.body(matchesJsonSchemaInClasspath("networkList.json"))
.body("networkList.keySet()",hasItems(
"name",
"authEnabled",
);
}
}
``
Another option is to use jsonPath similar to Spring Boot MockMvc ResultMatcher:
testImplementation 'com.jayway.jsonpath:json-path:2.4.0'
testImplementation 'org.hamcrest:hamcrest:2.2'
Get the response as HttpResponse<String> and then use JsonPath.parse(response.body()) to assert the json path:
#Test
public void verifyChannelStatusPackagesCanBeFetchedWithCompileTimeAutoGeneratedAtClient() {
URI uri = UriBuilder.of("/api/v1/channels/{stringParam}/status/").expand(singletonMap("stringParam", "value"));
HttpResponse<String> response = client.toBlocking().exchange(HttpRequest.GET(uri), String.class);
assertEquals(HttpStatus.OK, response.getStatus());
ReadContext ctx = JsonPath.parse(response.body());
assertThat(ctx.read("$"), isA(Object.class));
assertThat(ctx.read("$.chaincodeCount"), is("chaincodeCountValue"));
}
Example for an endpoint test using Micronaut vs Spring Boot

Xamarin SQLite database creating for all tables

I have working on this topic for 4 hours but I couldn't get any solution.
My problem is actually;
I have 5 table and I wanna create one controller to create different tables.
My current codes are below but this codes create only one table.
public interface ISQLite
{
SQLiteConnection GetConnection();
}
-
public class TodoItem
{
public TodoItem ()
{
}
[PrimaryKey, AutoIncrement]
public int ID { get; set; }
public string Name { get; set; }
public string Notes { get; set; }
public bool Done { get; set; }
}
-
public class TodoItemDatabase
{
static object locker = new object ();
SQLiteConnection database;
/// <summary>
/// Initializes a new instance of the <see cref="Tasky.DL.TaskDatabase"/> TaskDatabase.
/// if the database doesn't exist, it will create the database and all the tables.
/// </summary>
/// <param name='path'>
/// Path.
/// </param>
public TodoItemDatabase()
{
database = DependencyService.Get<ISQLite> ().GetConnection ();
// create the tables
database.CreateTable<TodoItem>();
}
public IEnumerable<TodoItem> GetItems ()
{
lock (locker) {
return (from i in database.Table<TodoItem>() select i).ToList();
}
}
public IEnumerable<TodoItem> GetItemsNotDone ()
{
lock (locker) {
return database.Query<TodoItem>("SELECT * FROM [TodoItem] WHERE [Done] = 0");
}
}
public TodoItem GetItem (int id)
{
lock (locker) {
return database.Table<TodoItem>().FirstOrDefault(x => x.ID == id);
}
}
public int SaveItem (TodoItem item)
{
lock (locker) {
if (item.ID != 0) {
database.Update(item);
return item.ID;
} else {
return database.Insert(item);
}
}
}
public int DeleteItem(int id)
{
lock (locker) {
return database.Delete<TodoItem>(id);
}
}
}
-
public class SQLite_Android : ISQLite
{
public SQLite_Android()
{
}
#region ISQLite implementation
public SQLite.SQLiteConnection GetConnection()
{
var sqliteFilename = "TodoSQLite.db3";
string documentsPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal); // Documents folder
var path = Path.Combine(documentsPath, sqliteFilename);
// This is where we copy in the prepopulated database
Console.WriteLine(path);
if (!File.Exists(path))
{
var s = Forms.Context.Resources.OpenRawResource(Resource.Raw.TodoSQLite); // RESOURCE NAME ###
// create a write stream
FileStream writeStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write);
// write to the stream
ReadWriteStream(s, writeStream);
}
var conn = new SQLite.SQLiteConnection(path);
// Return the database connection
return conn;
}
#endregion
/// <summary>
/// helper method to get the database out of /raw/ and into the user filesystem
/// </summary>
void ReadWriteStream(Stream readStream, Stream writeStream)
{
int Length = 256;
Byte[] buffer = new Byte[Length];
int bytesRead = readStream.Read(buffer, 0, Length);
// write the required bytes
while (bytesRead > 0)
{
writeStream.Write(buffer, 0, bytesRead);
bytesRead = readStream.Read(buffer, 0, Length);
}
readStream.Close();
writeStream.Close();
}
}
--- How can I create multi tables in one controller ?
Looks like you are using Sqlite.net-pcl, right?
Multiple tables from the same model are not supported (it's for simple cases only).
You can create multiple models (possibly by just inheriting) and then call CreatTable<T> for each of them.
I solved problem. Maybe this solution helps somenone.
I have two DbHepler Class and two model class for creating two tables on DB.
Base connection codes are same;
public interface ISQLite
{
SQLiteConnection GetConnection();
}
This is the App.cs file;
public class App : Application {
public App()
{
authenticationDB = new AuthenticationDbHelper(Database);
settingsDbHelper = new SettingsDbHelper(Database);
MainPage = new Views.MainMenuPage();
}
public static CreateDB Database
{
get
{
if (database == null)
{
database = new CreateDB();
}
return database;
}
}
}
The CreateDB class is necessary for create one db for all tables
public class CreateDB
{
public SQLiteConnection database;
public object locker = new object();
public CreateDB()
{
database = DependencyService.Get<ISQLite>().GetConnection();
}
}
This interface is necessary for created tables actions. Since implement this class we can use theese methods all tables.(T is table class)(To understand look AuthenticationDBHelper class)
public interface SQLiteBase<T>
{
IEnumerable<T> GetItems();
T GetItem(long id);
long SaveItem(T item);
void UpdateItem(T item);
int DeleteItem(int id);
int Clear();
int getCount();
}
This DbHelper class will be used for delete,insert,clear.... items.
public class AuthenticationDbHelper : SQLiteBase<AuthenticationDbTable>
{
SQLiteConnection database;
object locker;
public AuthenticationDbHelper(CreateDB db)
{
database = db.database;
locker = db.locker;
database.CreateTable<AuthenticationDbTable>();
}
public int Clear()
{
lock(locker)
{
return database.DeleteAll<AuthenticationDbTable>();
}
}
public int DeleteItem(int id)
{
lock (locker)
{
return database.Delete<AuthenticationDbTable>(id);
}
}
public AuthenticationDbTable GetItem(long id)
{
lock (locker)
{
return database.Table<AuthenticationDbTable>().FirstOrDefault(x => x.UserId == id);
}
}
public IEnumerable<AuthenticationDbTable> GetItems()
{
lock (locker)
{
return (from i in database.Table<AuthenticationDbTable>() select i).ToList();
}
}
public long SaveItem(AuthenticationDbTable item)
{
lock (locker)
{
return database.Insert(item);
}
}
public void UpdateItem(AuthenticationDbTable item)
{
lock(locker)
{
database.Update(item);
}
}
public int getCount()
{
return GetItems().Count();
}
}
I know it is very confused but this is the last. We will create model for authentication.
public class AuthenticationDbTable
{
public AuthenticationDbTable(long userId, string sessionId, string username, string clientuuid)
{
this.userId = userId;
this.sessionId = sessionId;
this.username = username;
this.clientuuid = clientuuid;
}
private long userId;
private string sessionId;
private string username;
private string clientuuid;
[PrimaryKey]
public long UserId
{
get { return userId; }
set { userId = value; }
}
public string SessionId
{
get { return sessionId; }
set { sessionId = value; }
}
public string Username
{
get { return username; }
set { username = value; }
}
public string Clientuuid
{
get { return clientuuid; }
set { clientuuid = value; }
}
}
Using
AuthenticationDbTable authentication = new AuthenticationDbTable(authenticateduser.User.UserId, r.Retval.SessionStatus.SessionId, authenticateduser.User.Name, authenticateduser.Clientuuid);
App.authenticationDB.SaveItem(authentiaction);
Note
For creating second table you can use same way. You should create second DbHelper and model class. Assume that you will create a table for settings. You should create SettingsDbHelper and SettingsDbTable class. through same way.
Thank you :)

PersistenceException: ERROR executing DML bindLog when Updating an Object

Good day! I have two objects: Tag and RelatedTag. The Tag can have many RelatedTags (which is also a Tag). Saving the Tag with its related tags works fine. But when I update the Tag, it has an error saying
[PersistenceException: ERROR executing DML bindLog[] error[Unique index or primary key violation: "PRIMARY KEY ON PUBLIC.RELATED_TAG(ID)"; SQL statement:\n insert into related_tag (id, tag_id, relationship, related_notes) values (?,?,?,?) [23505-172]]]
Here is Tag model:
package models;
import java.util.*;
import javax.persistence.*;
import javax.validation.*;
import play.data.Form;
import play.data.validation.Constraints.*;
import play.db.ebean.*;
import play.db.ebean.Model.Finder;
import scala.Int;
#Entity
public class Tag extends Model{
#Id
private int id;
#Required
#MaxLength(value=100)
private String name;
#MaxLength(value=200)
private String notes;
#OneToMany(cascade=CascadeType.ALL)
public List<RelatedTag> relatedTags = new ArrayList<RelatedTag>();
public static Finder<Integer, Tag> find = new Finder(Int.class, Tag.class);
public Tag() {
}
public Tag(String name, String notes){
this.name = name;
this.notes = notes;
}
public Tag(int id, String name, String notes, List<RelatedTag> relatedTags) {
this.id = id;
this.name = name;
this.notes = notes;
this.relatedTags = relatedTags;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNotes() {
return notes;
}
public void setNotes(String notes) {
this.notes = notes;
}
public List<RelatedTag> getRelatedTags() {
return relatedTags;
}
public void setRelatedTags(List<RelatedTag> relatedTags) {
this.relatedTags = relatedTags;
}
public static List<Tag> all() {
return find.all();
}
public static void create(Tag tag){
tag.save();
}
public static void delete(int id){
find.ref(id).delete();
}
public static void update(int id, Tag tag) {
tag.update(id); // updates this entity, by specifying the entity ID
}
public static boolean exists(Tag newTag) {
for(Tag allTags : Tag.find.all()) {
if(allTags.getName().equals(newTag.getName()))
return true;
}
return false;
}
}
And here is the RelatedTag model:
package models;
import java.util.*;
import javax.persistence.*;
import javax.validation.*;
import play.data.Form;
import play.data.validation.Constraints.*;
import play.db.ebean.*;
import play.db.ebean.Model.Finder;
import scala.Int;
#Entity
public class RelatedTag extends Model {
#Id
private int id;
private String relationship;
private String relatedNotes;
public RelatedTag() {}
public RelatedTag(int id, String relationship, String relatedNotes) {
this.id = id;
this.relationship = relationship;
this.relatedNotes = relatedNotes;
}
public void setId(int id){
this.id = id;
}
public void setRelationship(String relationship){
this.relationship = relationship;
}
public void setRelatedNotes(String relatedNotes) {
this.relatedNotes = relatedNotes;
}
public int getId(){
return id;
}
public String getRelationship(){
return relationship;
}
public String getRelatedNotes() {
return relatedNotes;
}
public static boolean exists(String tagRelated) {
for(Tag tag : Tag.find.all()) {
if(tagRelated.equals(tag.getName()))
return true;
}
return false;
}
public static RelatedTag findByLabel(String tagRelated, String relation, String relatedNotes) {
RelatedTag relatedTag = null;
for(Tag tag : Tag.find.all()) {
if(tagRelated.equals(tag.getName())) {
relatedTag = new RelatedTag(tag.getId(), relation, relatedNotes);
}
}
return relatedTag;
}
public static Tag findTag(int id) {
for(Tag tag : Tag.find.all()) {
if(id == tag.getId())
return tag;
}
return null;
}
}
When I run this (in which I update a Tag), the error happens.
private static void reciprocate(Tag tag) {
List<Tag> peers = new ArrayList<Tag>();
for (RelatedTag rt : tag.getRelatedTags()) {
if(rt.getRelationship().equals("peer"))
peers.add(RelatedTag.findTag(rt.getId()));
}
for(RelatedTag rt : tag.getRelatedTags()) {
int relTemp = 0;
String relation = new String();
if (rt.getRelationship().equals("parent"))
relTemp = 1;
if (rt.getRelationship().equals("child"))
relTemp = 2;
if (rt.getRelationship().equals("peer"))
relTemp = 3;
switch(relTemp) {
case 1: relation = "child"; break;
case 2: relation = "parent"; break;
case 3: relation = "peer"; break;
}
Tag related = new Tag();
related = Tag.find.byId(RelatedTag.findTag(rt.getId()).getId());
List<RelatedTag> available = new ArrayList<RelatedTag>();
List<String> availableName = new ArrayList<String>();
for (RelatedTag rt2 : related.getRelatedTags()) {
availableName.add(RelatedTag.findTag(rt2.getId()).getName());
}
if(availableName.contains(tag.getName())) {
for(RelatedTag rt2 : related.getRelatedTags()) {
if(!RelatedTag.findTag(rt2.getId()).getName().equals(tag.getName())) {
available.add(rt2);
}
}
}
available.add(RelatedTag.findByLabel(
tag.getName(), relation,
rt.getRelatedNotes()));
related.setRelatedTags(available);
related.update(related.getId()); //HERE
}
}
Please help me figure this out. After the first rt has been iterated, there goes the error but it saves its related tag. Thank you very much.
Your method RelatedTag#findByLabel always creates new RelatedTags with the IDs of the Tag class; if you have 2 related tags for the same tag, it will produce 2 related tags with the same ID.
Look into #GeneratedValue and EntityManager#createQuery.

Displaying Error in a View

Is there any standard practice to display errors in a view? Currently it is being displayed from TempData.
I implemented a derived a class from Base Controller and used that derived class in every one of my controller. Then assign error or success messages from controller.
public class TestController : Controller
{
public string ErrorMessage
{
get { return (string) TempData[CommonHelper.ErrorMessageKey]; }
set
{
if (TempData.ContainsKey(CommonHelper.ErrorMessageKey))
{
TempData[CommonHelper.ErrorMessageKey] = value;
}
else
{
TempData.Add(CommonHelper.ErrorMessageKey,value);
}
TempData.Remove(CommonHelper.SuccessMessageKey);
}
}
public string SuccessMessage
{
get { return (string)TempData[CommonHelper.SuccessMessageKey]; }
set
{
if(TempData.ContainsKey(CommonHelper.SuccessMessageKey))
{
TempData[CommonHelper.SuccessMessageKey] = value;
}
else
{
TempData.Add(CommonHelper.SuccessMessageKey, value);
}
TempData.Remove(CommonHelper.ErrorMessageKey);
}
}
}
CommonHelper Class
public class CommonHelper
{
public const string SuccessMessageKey = "successMessage";
public const string ErrorMessageKey = "errorMessage";
public static string GetSuccessMessage(object data)
{
return data == null ? string.Empty : (string) data;
}
public static string GetErrorMessage(object data)
{
return data == null ? string.Empty : (string) data;
}
}
Then created a partial view having this
#using Web.Helpers
#if (!string.IsNullOrEmpty(CommonHelper.GetSuccessMessage(TempData[CommonHelper.SuccessMessageKey])))
{
<div class="alert alert-success">
#CommonHelper.GetSuccessMessage(TempData[CommonHelper.SuccessMessageKey])
</div>
}
else if (!string.IsNullOrEmpty(CommonHelper.GetErrorMessage(TempData[CommonHelper.ErrorMessageKey])))
{
<div class="alert alert-success">
#CommonHelper.GetErrorMessage(TempData[CommonHelper.ErrorMessageKey])
</div>
}
And in every view, the partial view is rendered.
<div>
#Html.Partial("_Message")
</div>
Here is a pretty common implementation of displaying errors.
Controller
public class UserController : Controller
{
[HttpPost]
public ActionResult Create(User model)
{
// Example of manual validation
if(model.Username == "Admin")
{
ModelState.AddModelError("AdminError", "Sorry, username can't be admin")
}
if(!ModelState.IsValid()
{
return View(model)
}
}
}
Model
public class User
{
[Required]
public string Username {get; set;}
public string Name {get; set; }
}
View
#Html.ValidationSummary(true)
#using(Html.BeginForm())
{
// Form Html here
}
You don't need all of the infrastructure you created. This is handled by the framework. If you need a way to add success messages you can checkout the Nuget Package MVC FLASH
I prefer to use ModelState.AddModelError()