2

Been working on a snippet of java code to export a few tables from an accessdb to CSVs. I want to deploy this code as a Lambda function. I've tried using Jackcess, but the following

        try {
            String dateOfExtraction = LocalDateTime.now().toString();
            Database db = DatabaseBuilder.open(new File("java-runtime/src/access_db_file.accdb"));
            System.out.println(db.getTableNames());
            ExportUtil.exportFile(db, "table_name", new File("table_name" + dateOfExtraction + ".csv"));
        } catch (IOException e) {
            e.printStackTrace();
        }

throws the error: java.io.FileNotFoundException: given file does not exist: C:\Users\john.doe.ctr\Desktop\Work\table_name

I am running my code on a mac, this filepath is from the user that provided me with the DB. Is that some kind of permissions error? Should I just use UCanAccess instead? I can't use any of the UCanAccess command line tools, I have to run this in a lambda. The System.out.println(db.getTableNames()); line works exactly as expected, and prints a list of all of the tablenames in the accessdb.

Ravmcgav
  • 183
  • 1
  • 1
  • 11

2 Answers2

3

There can be several problems in the code.

The first, you are using LocalDateTime.now().toString() as part of the filename of the CSV file in which the information will be saved. It will give you something like:

2021-05-02T23:42:03.282

In some operating systems - you mentioned MacOS but it should allow you to create a file with that name - this name can be a possible cause of problems; please consider use something less error prone like System.currentTimeMillis:

String filename = "table_name" + System.currentTimeMillis() + ".csv";
ExportUtil.exportFile(db, "table_name", new File(filename));

Having said that, please, be aware that in the AWS Lambda function you probably will need to store your results in external storage, typically S3: you have the ability to write to the filesystem but it is usually of relevance when working with temporary files, not with persistent storage. Please, consider for instance the following code snippet.

// Here you can have a problem as well when trying to access the filesystem
// to read the Access file, but the API does not give you another option
// Probably deploying (https://docs.aws.amazon.com/lambda/latest/dg/lambda-java.html)
// your lambda function as a container (https://docs.aws.amazon.com/lambda/latest/dg/java-image.html) 
// and include your database file
Database db = DatabaseBuilder.open(new File("java-runtime/src/access_db_file.accdb"));
System.out.println(db.getTableNames());
String filename = "table_name" + System.currentTimeMillis() + ".csv";
// Instead of writing to a file, write to a byte array through a writer
try (ByteArrayOutputStream output = new ByteArrayOutputStream();
     BufferedWriter writer = new BufferedWriter(
         new OutputStreamWriter(output));
) {
  // Dump data
  ExportUtil.exportWriter(db, "table_name", writer);
  // Just in case
  writer.flush();
  // Get actual information
  byte[] data = output.toByteArray();
  // Save data to S3: please, consider refactor and organize the code
  S3Client s3 = ...; // Initialize as appropriate
  String bucketName = "your-bucket";
  String objectKey = filename; // object key, same as filename, for example
  // Perform actual S3 request
  PutObjectResponse response = s3.putObject(
    PutObjectRequest.builder()
      .bucket(bucketName)
      .key(objectKey)
      .build(),
    RequestBody.fromBytes(data)
  );
} catch (IOException e) {
  e.printStackTrace();
}

From a totally different perspective, the problem can be caused because table_name is a linked table. When you create a linked table, you need to define the path to the linked information: in your case, probably this information is stored in C:\Users\john.doe.ctr\Desktop\Work\table_name in the original computer of your client.

If you have the MS Access program, you can verify if that is the actual problem with the help of Linked Table Manager.

If you do not have the MS Access program, you can use the Database class as well. Please, consider the following example:

Database db = DatabaseBuilder.open(new File("java-runtime/src/access_db_file.accdb"));
Table table = db.getTable("table_name");
boolean isLinkedTable = db.isLinkedTable(table);

If the table is linked you need two things: on one hand, the linked information itself and, on the other, you need to provide a convenient implementation of the LinkResolver interface, probably by extending CustomLinkResolver. This interface basically provides you the ability to map the location of a linked table to a different path. Please, consider review this test for a convenient example of such as implementation.

For instance, think in something like this:

public class RemapLinkResolver implements LinkResolver {

  // Maintain a correspondence between the original linked db file
  // and the same db in your new filesystem 
  private Map<String, String> dbLinkeeFileRemap = new HashMap<>();

  public void remap(String originalLinkeeFileName, String newLinkeeFileName) {
    this.dbLinkeeFileRemap.put(originalLinkeeFileName, newLinkeeFileName);
  }

  @Override
  public Database resolveLinkedDatabase(Database linkerDb,
                                        String linkeeFileName)
    throws IOException {
    // if linker is read-only, open linkee read-only
    boolean readOnly = ((linkerDb instanceof DatabaseImpl) ?
                       ((DatabaseImpl)linkerDb).isReadOnly() : false);
    String newLinkeeFileName = this. dbLinkeeFileRemap.get(linkeeFileName);
    if (newLinkeeFileName != null) {
      return new DatabaseBuilder(new File(newLinkeeFileName))
        .setReadOnly(readOnly).open();
    }

    // Fallback to default
    return LinkResolver.DEFAULT.resolveLinkedDatabase(linkerDb, linkeeFileName);
  }
}

Then, use it in your code:

Database db = DatabaseBuilder.open(new File("java-runtime/src/access_db_file.accdb"));
RemapLinkResolver linkResolver = new RemapLinkResolver();
linkResolver.remap(
  "C:\Users\john.doe.ctr\Desktop\Work\table_name",
  "java-runtime/src/table_name.accdb"
);
db.setLinkResolver(linkResolver);
// Continue as usual

I hope you get the idea, please, adapt the paths and, in general, the code as appropriate.

jccampanero
  • 50,989
  • 3
  • 20
  • 49
  • this doesn't work -- yields the exact same error as before. Don't think it has anything to do with the way I was writing to the CSV but instead is an issue with ```getTable```. I included the stacktrace above! – Ravmcgav May 03 '21 at 06:26
  • Thank you very much for the feedback @Ravmcgav. Yes, it is a possibility indeed. I updated my answer with further information related with the use of linked tables. I hope it helps. – jccampanero May 03 '21 at 13:14
  • @DerekO Ravmcgav Is the assumption of linked tables valid? Were you able to test the `LinkResolver` based solution? Did it work? Please, be aware that I was unable to actually test the code: if necessary, try debugging the proposed `resolveLinkedDatabase` method to be able to correctly map the information provided by Jackcess and the actual alternative database path. I hope it helps. – jccampanero May 07 '21 at 17:19
  • Thank you very much @Derek_O. But mate, did you actually solve the problem? Is the problem really related with linked tables? – jccampanero May 10 '21 at 21:28
  • Hey sorry just getting back to this...I never got it figured out. After calling your class, ```getTable()``` no longer fails but the table that gets pulled out is ```null``` – Ravmcgav May 14 '21 at 22:50
  • There is no need to apologize @Ravmcgav. I see:. If you debug the code in `resolveLinkedDatabase` with your actual database, does the `linkeeFileName` parameter provide a meaningful information? Is that value mapped accordingly? I suppose that it is not possible, but can you share your actual access file? without data of course, just the structure of tables. – jccampanero May 15 '21 at 13:45
  • What do you mean by structure? Like the tablenames? ```linkeeFileName``` doesn't provide much meaningful info, but I believe I am mapping it correctly! – Ravmcgav May 17 '21 at 18:17
  • Yes, exactly @Ravmcgav, that is what I mean. I am sorry to hear that it is not working properly. Please, can you share a copy of your actual database? Just a minimum reproducible example to test the error, without data, of course, and only with the table that is causing the issue. Is it possible? I think it can be helpful. – jccampanero May 17 '21 at 21:09
  • yes please try to reproduce -- let me know how I can help! – Ravmcgav May 19 '21 at 04:16
1

Jccs advice looks pretty solid. Can you try/confirm/elaborate on some of the following, too?

  1. One thing is you said you the code on YOUR mac but the filepath is from ANOTHER user. Are you somehow implicitly or explicitly referencing a folder that's not available on your Mac and hence you can't access an invalid folder path? Jackcess would probably be OK creating a file in a folder, but if a chunk of the parent folder path for the export is missing, maybe it can't or doesn't build up the necessary sub-folders to create the file and throws error implicitly?

  2. Is there extra configuration the Lambda needs to access the folder paths at play in the previous comment given that I imagine it runs in a cloud stack?

  3. Does jackess require you initial/create the file in place before it opens it for file writing..? Doesn't seem like it from the API.

4.Can you temporarily hardcode new File("table_name" + dateOfExtraction + ".csv")" to instead be something simple like new File("steve.csv"). I am specifically curious to see if your error message updates accordingly to complain it can't access steve.csv in that folder.

Atmas
  • 2,389
  • 5
  • 13