cloudant / java-cloudant

A Java client for Cloudant
Apache License 2.0
79 stars 68 forks source link

Unable to use database.find on documents with a + in the document id. #373

Closed Andrew-Roy closed 7 years ago

Andrew-Roy commented 7 years ago

CloudantClient version 2.9.0 Java: 1.8.0_141 hotSpot 64-Bit (build 25.141-b15) on Windows 7

database.find fails with 404 when requesting a document with a '+' sign in the document id.

Other information: When using database.save on an object with a '+' sign in the document id, the stored document actually has a space ' ' for the '+' sign. Using find on an id with a '+' will retrieve that record.

But this leaves the problem of some other application storing documents with '+' characters in their id. I do not know how to use this project to retrieve such a document, a 404 is returned. I have attempted HTTP escaping the '+' and that does not work.

The sample code below attempts to create a document with an id: "Foo 2017-05-06T21:53:41.9776348+00:00"

But the actual document created is: { "_id": "Foo 2017-05-06T21:53:41.9776348 00:00", "_rev": "1-967a00dff5e02add41819138abb3284d" }

The "+00:00" was actually stored as " 00:00". This leaves the issue of how to use .find to retrieve documents with a '+' in the id (these documents were stored via software that does not use this library and the actual document ids cannot be changed to remove the '+').

Sample Code:

import java.net.URL;

import com.cloudant.client.api.ClientBuilder; import com.cloudant.client.api.CloudantClient; import com.cloudant.client.api.Database;

public class Main { public CloudantClient client;

private static String   noSQLurl    = "redacted -- cloudant URL --";
private static String   user        = "redacted -- cloudant user --";
private static String   password    = "redacted -- cloudant password --";

public static void main(String[] args)
{
    Main m = new Main();
    try
    {
        m.doWork(args);
    }
    catch (Exception e)
    {
        System.err.println("Significant error: ");
        e.printStackTrace();
    }
}

public void doWork(String[] args) throws Exception
{
    String badId = "Foo 2017-05-06T21:53:41.9776348+00:00";

    if (performSave(badId))
        performFind(badId);
}

public boolean performSave(String id) throws Exception
{
    Database db = getDB("temp-table", true);

    try
    {
        System.out.println("Saving document with id = \"" + id + "\"");
        db.save(new ExampleDocument(id));
        System.out.println("Save successful");

        return true;
    }
    catch (Exception e)
    {
        System.err.println("Save failed");
        e.printStackTrace();

        return false;
    }
}

public boolean performFind(String id) throws Exception
{
    Database db = getDB("temp-table", false);

    try
    {
        System.out.println("Finding document with id = \"" + id + "\"");
        db.find(id);
        System.out.println("Find successful");

        return true;
    }
    catch (Exception e)
    {
        System.err.println("Find failed");
        e.printStackTrace();

        return false;
    }
}

public Database getDB(String databaseName, boolean create) throws Exception
{
    if (client == null)
    {
        client = ClientBuilder.url(new URL(noSQLurl)).username(user).password(password).build();
    }

    return client.database(databaseName, create);
}

public class ExampleDocument
{
    private String  _id     = null;
    private String  _rev    = null;

    public ExampleDocument(String id)
    {
        _id = id;
    }

    public String toString()
    {
        return " { id: " + _id + ", rev: " + _rev + "}";
    }
}

}

tomblench commented 7 years ago

@Andrew-Roy as you suspect this looks like a URL escaping issue, and unfortunately escaping it yourself won't work because then you'll get double-escaping. I'll see if I can reproduce it and get back to when I have more information.

tomblench commented 7 years ago

@Andrew-Roy I tried to reproduce your issue, using your code, and the document saved with the correct docid.

I wonder if you are picking up an older version of our jar, perhaps in a cache somewhere? This bug was fixed in version 2.8.0. If you're happy disclosing your account name to us, we can check our logs for the user-agent header which will contain the version.

Andrew-Roy commented 7 years ago

@tomblench I deleted all the jars and reran the maven build. Everything says I am using 2.9.0.

I reran my test, which created the document without the '+'. I then manually created another document with the '+' in the id and deleted the original document (with the '+'). When attempting to .find that document I get:

com.cloudant.client.org.lightcouch.NoDocumentException: 404 Object Not Found: not_found: deleted at com.cloudant.client.org.lightcouch.CouchDbClient.execute(CouchDbClient.java:511) at com.cloudant.client.org.lightcouch.CouchDbClient.executeToInputStream(CouchDbClient.java:574) at com.cloudant.client.org.lightcouch.CouchDbClient.get(CouchDbClient.java:320) at com.cloudant.client.org.lightcouch.CouchDatabaseBase.find(CouchDatabaseBase.java:148) at com.cloudant.client.api.Database.find(Database.java:612) at Main.performFind(Main.java:67) at Main.doWork(Main.java:33) at Main.main(Main.java:20)

As it references that the document is deleted, and does not find the one with the matching id, I assume the '+' is getting escaped out somewhere.

I can skip maven and pull the known jars just in case something is getting confused.

Thank you for your efforts.