RxJava2 complex use case - rx-java2

Firstly, I have a API to download an Article object that contains a list of Attachments:
// retrofit article endpoint
#GET("/article")
Single<Article> getArticle();
// article class
public class Article {
List<? extends Attachment> attachments;
}
However, the attachment has different subclass such as Video, TextFile etc.
For TextFile, it only has an id while text content needs to be downloaded from another API by using the id:
// retrofit text content endpoint
#GET("/text/{id}")
Single<String> getTextContent(#Path("id") int textFileId);
// text file class
public class TextFile extends Attachment {
// we get id from getArticle API and it's used for download content
private int id;
// we get content from getTextContent API
private String content;
// getters, setters...
}
After I downloaded the text content string for each TextFile object in the attachment list, I need to use set them to back to the corresponding TextFile object list, and finally return the Article object to be displayed.
Currently, what I'm doing is:
public Single<Article> getArticleForView() {
return service.getArticle()
.somehowChainWithGetTextFileWithContent?
}
// method to download text content and set it to TextFile object in the attachment list
private Flowable<TextFile> getTextFileWithContent(List<? extends Attachment> attachments) {
return Flowable.fromIterable(attachments)
// filter out non TextFile attachments
.filter(attachment -> attachment instanceof TextFile)
// download text content and set to TextFile
.concatMap(attachment -> {
TextFile textFile = (TextFile) attachment;
return service.getTextContent(textFile.getId())
.map(textContent -> {
textFile.setContent(textContent);
return textFile;
})
.toFlowable();
});
}
So, I wonder is there any RxJava2 operators can chain those calls to fit in the use case I have? Any help will be appreciated.
Edited:
So this is solution I end up with:
First, I changed the Attachment list in Article class to set, So I don't have to worry about merging updated TextFiles into Article object. But obviously I need to override equals() method in Attachment Class to maintain uniqueness by id.
// article class
public class Article {
Set<? extends Attachment> attachments;
}
And I create a method to take a TextFile object (without content) and return a Single of updated TextFile object:
private Single<TextFile> getTextFileWithContent(TextFile textFile) {
return service.getTextContent(textFile.getId())
.map(content -> {
textFile.setContent(content);
return textFile;
});
}
Then another method takes an Article object and returns a Single> which I will chain to getArticle() call:
private Single<Set<TextFile>> getArticleTextFileDetails(Article article) {
Set<Attachment> attachments = article.getAttachments();
if(attachments == null || attachments.isEmpty()) {
return Single.just(Collections.emptySet());
}
// initial item for collectInto operator
Set<TextFile> updatedTextFiles = new HashSet<>();
return Flowable.fromIterable(attachments)
// filter out non TextFile attachments
.filter(attachment -> attachment instanceof TextFile)
// download content and set to TextFile
.flatMapSingle(this::getTextFileWithContent)
// collect all emits from Flowable into a Single
.collectInto(updatedTextFiles, Set::add);
}
After I got that Set of updated TextFiles, I simply chain it with getArticle() method by flatMap operator:
public Single<Article> getArticleForView() {
return service.getArticle()
.flatMap(article -> {
return getArticleTextFileDetails(article)
.map(updatedTextFileSet -> {
if(!updatedTextFileSet.isEmpty()) {
// simply add updated TextFile set to Attachment set
// since we already override equals() method in Attachment class
article.addAttachments(updatedTextFileSet);
}
return article;
})
});
}

Related

How can I return only several Json fields of an inner object with Jackson

I'm using Jackson + spring RestControlles
Lets say I have 3 classes
class Card {
String id;
String pan;
String holder;
String expiry;
}
class CardProfile {
String id;
Card card;
...
}
class Recipient {
String name;
Card card;
...
}
So when I serialize a CardProfile object I want all fields of object card to be percent in result json.
But in case Recipient object , Json has to have only part of object card(For example : pan, holder) .
Is there a way to present Card object in json with different set of fields?
Use of annotations like #JsonIgnore will remove fields from both cases.
Thanks.
Thinking again - is this approach scalable? What if you need something similar again?
Why not rely on inheritance and create a base class and multiple subclasses with attributes you are interested in?
class Card{
// bare min attribute
.....
}
class SubCard1 extends Card{
//add more attribute
.....
}
class SubCard2 extends Card{
//add more attribute
.....
}
Use SubCard1 or SubCard2 based on what you need? Same can be used for other classes. If this is more dynamic - look at design patterns.
when you serialize the object you can keep only the parameter that are interest for you.
I post a simple java code to resolve this stuff you can convert the code in the lenguage that you need.
the following code is prt of the Recipient class which contains two field: String name and Card card
To write the json object:
#Override
public final void toJSON(final OutputStream out) throws IOException {
final JsonGenerator jg = JSON_FACTORY.createGenerator(out);
jg.writeStartObject();
jg.writeFieldName("Recipient");
jg.writeStartObject();
jg.writeNumberField("id",card.getID());
jg.writeStringField("name", name);
jg.writeStringField("pan", card.getPan());
jg.writeStringField("holder", card.getHolder());
jg.writeEndObject();
jg.writeEndObject();
jg.flush();
}
to get the the object from the json:
public static Recipient fromJSON(final InputStream in) throws IOException {
int jId = -1;
String jName = null;
String jHolder = null;
String jPan = null;
//Obtain a new JsonParser to parse Recipient from JSON
final JsonParser jp = JSON_FACTORY.createParser(in);
while (jp.getCurrentToken() != JsonToken.FIELD_NAME || "Recipient".equals(jp.getCurrentName()) == false) {
// there are no more events
if (jp.nextToken() == null) {
throw new IOException("Unable to parse JSON: no employee object found.");
}
}
while (jp.nextToken() != JsonToken.END_OBJECT) {
if (jp.getCurrentToken() == JsonToken.FIELD_NAME) {
switch (jp.getCurrentName()) {
case "id":
jp.nextToken();
jId = jp.getIntValue();
break;
case "name":
jp.nextToken();
jName = jp.getText();
break;
case "pan":
jp.nextToken();
jPan = jp.getText();
break;
case "holder":
jp.nextToken();
jHolder = jp.getText();
break; }
} }
//create new card object, the values not present in the json will set to null
Card card = new Card(jId,null,jPan,jHolder,null);
return new Recipient(jName, card);
}
}
I tried to adapt the code for you but it is just to give you an idea,I hope this will be usefull for you.

fiddler - decode custom encrypted body in inspector by c# on the fly

The content is Encrypt with an CUSTOM encrypter. fiddler captured content body is base64 encode string in plain text
The application traffic flow:
request:
base64Encode(customEncryptFromStringTobytes(jsonString) ) -> application -> http server
response:
customDecryptFrombytesToString(base64Decode(jsonString) ) <- application <- http server
I have the encrypt/decrypt class in c#:
string EncryptToBase64(string plainText);
string DecryptFromBase64(string plainText);
I build an exe to do the transform, I wonder how To make fiddler decode request/respose body by this exe on the fly
I want Fiddler show decrypt content in inspector , and encrypt again everytime I [Reissue and Edit(E)] the request.
I found something close but Dont know how to call an exe to do decode.
http://docs.telerik.com/fiddler/KnowledgeBase/FiddlerScript/ModifyRequestOrResponse
update:
I have implement the custom inspector for Fiddler. see the answer below.
I create an Extension for custom Inspector
full example https://github.com/chouex/FiddlerExtensionExample
you will build a dll and copy the file to Inspectors and Scripts folder in fiddler. restart fiddler will load the extenion.
note:
I used pre/post-build script to copy dll and restart fiddler in vs project.
custom inspector:
This Example just beautify the json body.
public class ResponseInspector : Inspector2, IResponseInspector2
{
TextBox myControl;
private byte[] m_entityBody;
private bool m_bDirty;
private bool m_bReadOnly;
public bool bReadOnly
{
get { return m_bReadOnly; }
set
{
m_bReadOnly = value;
// TODO: You probably also want to turn your visible control CONFIG.colorDisabledEdit (false) or WHITE (true) here depending on the value being passed in.
}
}
public void Clear()
{
m_entityBody = null;
m_bDirty = false;
myControl.Text = "";
}
public ResponseInspector()
{
// TODO: Add constructor logic here
}
public HTTPResponseHeaders headers
{
get { return null; // Return null if your control doesn't allow header editing.
}
set { }
}
public byte[] body
{
get { return m_entityBody; }
set
{
// Here's where the action is. It's time to update the visible display of the text
m_entityBody = value;
if (null != m_entityBody)
{
var text = System.Text.Encoding.UTF8.GetString(m_entityBody);
if (!String.IsNullOrEmpty(text) && text.StartsWith("{"))
{
text = JsonConvert.SerializeObject(JsonConvert.DeserializeObject(text), Formatting.Indented);
}
myControl.Text = text;
// TODO: Use correct encoding based on content header.
}
else
{
myControl.Text = "";
}
m_bDirty = false;
// Note: Be sure to have an OnTextChanged handler for the textbox which sets m_bDirty to true!
}
}
public bool bDirty
{
get { return m_bDirty; }
}
public override int GetOrder()
{
return 0;
}
public override void AddToTab(System.Windows.Forms.TabPage o)
{
myControl = new TextBox(); // Essentially the TextView class is simply a usercontrol containing a textbox.
myControl.Height = o.Height;
myControl.Multiline = true;
myControl.ScrollBars = ScrollBars.Vertical;
o.Text = "TextViewExample";
o.Controls.Add(myControl);
o.Controls[0].Dock = DockStyle.Fill;
}
}
for Traffic Tamper(not mention in question but I think this is useful):
implement AutoTamperResponseBefore() in IAutoTamper2
This Example just replace any text from "xt" to "c1" in every request body
Add a dummy header 
Fiddler-Encoding: base64
 and encode your body using base64 if it contains any binary data. Fiddler will decode the data before transmitting it to the server.
Taken from http://www.fiddlerbook.com/fiddler/help/composer.asp

Rewrite Adobe CQ Image src attribute

In AEM, content such as pages and images contains the '/content/' prefix in them. We are able to rewrite these url via Link Checker Transformer configuration and resourceResolver.map() method. URLs are being rewritten for HTML elements <a> and <form>.
But I want it to work for <img> elements as well.
I tried including the <img> elements to the Link Checker Transformer configuration by adding it to the 'Rewrite Elements' list as img:src:
I also checked the answers from What am I missing for this CQ5/AEM URL rewriting scenario? but both attempts didn't work for this issue.
Is there any way to do this?
Even if the rewriter and Link Checker Transformer didn't work. I used a custom LinkRewriter by using the Transformer and TransformerFactory interfaces. I based on the sample from Adobe for my code. I worked out something like this:
#Component(
metatype = true,
label = "Image Link Rewriter",
description = "Maps the <img> elements src attributes"
)
#Service(value = TransformerFactory.class)
#Property(value = "global", propertyPrivate = true)
public class ImageLinkRewriter implements Transformer, TransformerFactory {
// some variables
public CustomLinkTransformer() { }
#Override
public void init(ProcessingContext context,
ProcessingComponentConfiguration config) throws IOException {
// initializations here
}
#Override
public final Transformer createTransformer() {
return new CustomLinkTransformer();
}
#Override
public void startElement(String uri, String localName,
String qName, Attributes atts) throws SAXException {
if ("img".equalsIgnoreCase(localName)) {
contentHandler.startElement(uri, localName, qName, rewriteImageLink(atts));
}
}
private Attributes rewriteImageLink(Attributes attrs) {
String attrName = "src";
AttributesImpl result = new AttributesImpl(attrs);
String link = attrs.getValue(attrName);
String mappedLink = resource.getResourceResolver().map(request, link);
result.setValue(result.getIndex(attrName), mappedLink);
return result;
}
}
Hope this helps others. Here are a few references:
TransformerFactory interface
Transformer interface
Adobe

How to POST InputStream as the body of a request in Retrofit?

I'm attempting to do a POST with the body being an InputStream with something like this:
#POST("/build")
#Headers("Content-Type: application/tar")
Response build(#Query("t") String tag,
#Query("q") boolean quiet,
#Query("nocache") boolean nocache,
#Body TypedInput inputStream);
In this case the InputStream is from a compressed tar file.
What's the proper way to POST an InputStream?
You can upload inputStream using Multipart.
#Multipart
#POST("pictures")
suspend fun uploadPicture(
#Part part: MultipartBody.Part
): NetworkPicture
Then in perhaps your repository class:
suspend fun upload(inputStream: InputStream) {
val part = MultipartBody.Part.createFormData(
"pic", "myPic", RequestBody.create(
MediaType.parse("image/*"),
inputStream.readBytes()
)
)
uploadPicture(part)
}
If you want to find out how to get an image Uri, check this answer: https://stackoverflow.com/a/61592000/10030693
TypedInput is a wrapper around an InputStream that has metadata such as length and content type which is used in making the request. All you need to do is provide a class that implements TypedInput which passed your input stream.
class TarFileInput implements TypedInput {
#Override public InputStream in() {
return /*your input stream here*/;
}
// other methods...
}
Be sure you pass the appropriate return values for length() and mimeType() based on the type of file from which you are streaming content.
You can also optionally pass it as an anonymous implementation when you are calling your build method.
The only solution I came up with here was to use the TypeFile class:
TypedFile tarTypeFile = new TypedFile("application/tar", myFile);
and the interface (without explicitly setting the Content-Type header this time):
#POST("/build")
Response build(#Query("t") String tag,
#Query("q") boolean quiet,
#Query("nocache") boolean nocache,
#Body TypedInput inputStream);
Using my own implementation of TypedInput resulted in a vague EOF exception even while I provided the length().
public class TarArchive implements TypedInput {
private File file;
public TarArchive(File file) {
this.file = file;
}
public String mimeType() {
return "application/tar";
}
public long length() {
return this.file.length();
}
public InputStream in() throws IOException {
return new FileInputStream(this.file);
}
}
Also, while troubleshooting this issue I tried using the latest Apache Http client instead of OkHttp which resulted in a "Content-Length header already present" error even though I wasn't explicitly setting that header.
According to the Multipart section of http://square.github.io/retrofit/ you'll want to use TypedOutput instead of TypedInput. Following their examples for multipart uploads worked fine for me once I had implemented a TypedOutput class.
My solution was to implement TypedOutput
public class TypedStream implements TypedOutput{
private Uri uri;
public TypedStream(Uri uri){
this.uri = uri;
}
#Override
public String fileName() {
return null;
}
#Override
public String mimeType() {
return getContentResolver().getType(uri);
}
#Override
public long length() {
return -1;
}
#Override
public void writeTo(OutputStream out) throws IOException {
Utils.copyStream(getContentResolver().openInputStream(uri), out);
}
}

Need help loading XML data into XNA 4.0 project

I'd like to do this the right way if possible. I have XML data as follows:
<?xml version="1.0" encoding="utf-8"?>
<XnaContent>
<Asset Type="PG2.Dictionary">
<Letters TotalInstances="460100">
<Letter Count="34481">a</Letter>
...
<Letter Count="1361">z</Letter>
</Letters>
<Words Count="60516">
<Word>aardvark</Word>
...
<Word>zebra</Word>
</Words>
</Asset>
</XnaContent>
and I'd like to load this in (using Content.Load< Dictionary >) into one of these
namespace PG2
{
public class Dictionary
{
public class Letters
{
public int totalInstances;
public List<Character> characters;
public class Character
{
public int count;
public char character;
}
}
public class Words
{
public int count;
public HashSet<string> words;
}
Letters letters;
Words words;
}
}
Can anyone help with either instructions or pointers to tutorials? I've found a few which come close but things seem to have changed slightly between 3.1 and 4.0 in ways which I don't understand and a lot of the documentation assumes knowledge I don't have. My understanding so far is that I need to make the Dictionary class Serializable but I can't seem to make that happen. I've added the XML file to the content project but how do I get it to create the correct XNB file?
Thanks!
Charlie.
This may help Link. I found it useful to work the other way round to check that my xml data was correctly defined. Instantate your dictionary class set all the fields then serialize it to xml using a XmlSerializer to check the output.
You need to implement a ContentTypeSerializer for your Dictionary class. Put this in a content extension library and add a reference to the content extension library to your content project. Put your Dictionary class into a game library that is reference by both your game and the content extension project.
See:
http://blogs.msdn.com/b/shawnhar/archive/2008/08/26/customizing-intermediateserializer-part-2.aspx
Here is a quick ContentTypeSerializer I wrote that will deserialize your Dictionary class. It could use better error handling.
using System;
using System.Collections.Generic;
using System.Xml;
using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Intermediate;
namespace PG2
{
[ContentTypeSerializer]
class DictionaryXmlSerializer : ContentTypeSerializer<Dictionary>
{
private void ReadToNextElement(XmlReader reader)
{
reader.Read();
while (reader.NodeType != System.Xml.XmlNodeType.Element)
{
if (!reader.Read())
{
return;
}
}
}
private void ReadToEndElement(XmlReader reader)
{
reader.Read();
while (reader.NodeType != System.Xml.XmlNodeType.EndElement)
{
reader.Read();
}
}
private int ReadAttributeInt(XmlReader reader, string attributeName)
{
reader.MoveToAttribute(attributeName);
return int.Parse(reader.Value);
}
protected override Dictionary Deserialize(IntermediateReader input, Microsoft.Xna.Framework.Content.ContentSerializerAttribute format, Dictionary existingInstance)
{
Dictionary dictionary = new Dictionary();
dictionary.letters = new Dictionary.Letters();
dictionary.letters.characters = new List<Dictionary.Letters.Character>();
dictionary.words = new Dictionary.Words();
dictionary.words.words = new HashSet<string>();
ReadToNextElement(input.Xml);
dictionary.letters.totalInstances = ReadAttributeInt(input.Xml, "TotalInstances");
ReadToNextElement(input.Xml);
while (input.Xml.Name == "Letter")
{
Dictionary.Letters.Character character = new Dictionary.Letters.Character();
character.count = ReadAttributeInt(input.Xml, "Count");
input.Xml.Read();
character.character = input.Xml.Value[0];
dictionary.letters.characters.Add(character);
ReadToNextElement(input.Xml);
}
dictionary.words.count = ReadAttributeInt(input.Xml, "Count");
for (int i = 0; i < dictionary.words.count; i++)
{
ReadToNextElement(input.Xml);
input.Xml.Read();
dictionary.words.words.Add(input.Xml.Value);
ReadToEndElement(input.Xml);
}
ReadToEndElement(input.Xml); // read to the end of words
ReadToEndElement(input.Xml); // read to the end of asset
return dictionary;
}
protected override void Serialize(IntermediateWriter output, Dictionary value, Microsoft.Xna.Framework.Content.ContentSerializerAttribute format)
{
throw new NotImplementedException();
}
}
}